Closure, 120

<FILEB>
<CHANGES>
if (ref.getSymbol().getScope()!= ref.scope) {
return false;
}
<CHANGEE>
<FILEE>
<FILEB> return references.get(i); } } return null; } /** * @return Whether the variable is only assigned a value once for its * lifetime. */ boolean isAssignedOnceInLifetime() { Reference ref = getOneAndOnlyAssignment(); if (ref == null) { return false; } // Make sure this assignment is not in a loop. for (BasicBlock block = ref.getBasicBlock(); block != null; block = block.getParent()) { if (block.isFunction) { <CHANGES> <CHANGEE> break; } else if (block.isLoop) { return false; } } return true; } /** * @return The one and only assignment. Returns if there are 0 or 2+ * assignments. */ private Reference getOneAndOnlyAssignment() { <FILEE> <SCANS>, Behavior behavior, Predicate<Var> varFilter) { this.compiler = compiler; this.behavior = behavior; this.varFilter = varFilter; } /** * Convenience method for running this pass over a tree with this * class as a callback. */ @Override public void process(Node externs, Node root) { NodeTraversal.traverseRoots( compiler, Lists.newArrayList(externs, root), this); } /** * Same as process but only runs on a part of AST associated to one script. */ @Override public void hotSwapScript(Node scriptRoot, Node originalRoot) { NodeTraversal.traverse(compiler, scriptRoot, this); } /** * Gets the variables that were referenced in this callback. */ @Override public Iterable<Var> getAllSymbols() { return referenceMap.keySet(); } @Override public Scope getScope(Var var) { return var.scope; } /** * Gets the reference collection for the given variable. */ @Override public ReferenceCollection getReferences(Var v) { return referenceMap.get(v); } /** * For each node, update the block stack and reference collection * as appropriate. */ @Override public void visit(NodeTraversal t, Node n, Node parent) { if (n.isName()) { Var v; if (n.getString().equals("arguments")) { v = t.getScope().getArgumentsVar(); } else { v = t.getScope().getVar(n.getString()); } if (v != null && varFilter.apply(v)) { addReference(v, new Reference(n, t, blockStack.peek())); } } if (isBlockBoundary(n, parent)) { blockStack.pop(); } } /** * Updates block stack and invokes any additional behavior. */ @Override public void enterScope(NodeTraversal t) { Node n = t.getScope().getRootNode(); BasicBlock parent = blockStack.isEmpty() ? null : blockStack.peek(); blockStack.push(new BasicBlock(parent, n)); } /** * Updates block stack and invokes any additional behavior. */ @Override public void exitScope(NodeTraversal t) { blockStack.pop(); if (t.getScope().isGlobal()) { // Update global scope reference lists when we are done with it. compiler.updateGlobalVarReferences(referenceMap, t.getScopeRoot()); behavior.afterExitScope(t, compiler.getGlobalVarReferences()); } else { behavior.afterExitScope(t, new ReferenceMapWrapper(referenceMap)); } } /** * Updates block stack. */ @Override public boolean shouldTraverse(NodeTraversal nodeTraversal, Node n, Node parent) { // If node is a new basic block, put on basic block stack if (isBlockBoundary(n, parent)) { blockStack.push(new BasicBlock(blockStack.peek(), n)); } return true; } /** * @return true if this node marks the start of a new basic block */ private static boolean isBlockBoundary(Node n, Node parent) { if (parent != null) { switch (parent.getType()) { case Token.DO: case Token.FOR: case Token.TRY: case Token.WHILE: case Token.WITH: // NOTE: TRY has up to 3 child blocks: // TRY // BLOCK // BLOCK // CATCH // BLOCK // Note that there is an explicit CATCH token but no explicit // FINALLY token. For simplicity

Closure, 120

<FILEB>
<CHANGES>
if (ref.getSymbol().getScope()!= ref.scope) {
return false;
}
<CHANGEE>
<FILEE>
<FILEB> return references.get(i); } } return null; } /** * @return Whether the variable is only assigned a value once for its * lifetime. */ boolean isAssignedOnceInLifetime() { Reference ref = getOneAndOnlyAssignment(); if (ref == null) { return false; } // Make sure this assignment is not in a loop. for (BasicBlock block = ref.getBasicBlock(); block != null; block = block.getParent()) { if (block.isFunction) { <CHANGES> <CHANGEE> break; } else if (block.isLoop) { return false; } } return true; } /** * @return The one and only assignment. Returns if there are 0 or 2+ * assignments. */ private Reference getOneAndOnlyAssignment() { <FILEE> <SCANS>, we consider each BLOCK // a separate basic BLOCK. return true; case Token.AND: case Token.HOOK: case Token.IF: case Token.OR: // The first child of a conditional is not a boundary, // but all the rest of the children are. return n != parent.getFirstChild(); } } return n.isCase(); } private void addReference(Var v, Reference reference) { // Create collection if none already ReferenceCollection referenceInfo = referenceMap.get(v); if (referenceInfo == null) { referenceInfo = new ReferenceCollection(); referenceMap.put(v, referenceInfo); } // Add this particular reference referenceInfo.add(reference); } interface ReferenceMap { ReferenceCollection getReferences(Var var); } private static class ReferenceMapWrapper implements ReferenceMap { private final Map<Var, ReferenceCollection> referenceMap; public ReferenceMapWrapper(Map<Var, ReferenceCollection> referenceMap) { this.referenceMap = referenceMap; } @Override public ReferenceCollection getReferences(Var var) { return referenceMap.get(var); } } /** * Way for callers to add specific behavior during traversal that * utilizes the built-up reference information. */ interface Behavior { /** * Called after we finish with a scope. */ void afterExitScope(NodeTraversal t, ReferenceMap referenceMap); } static final Behavior DO_NOTHING_BEHAVIOR = new Behavior() { @Override public void afterExitScope(NodeTraversal t, ReferenceMap referenceMap) {} }; /** * A collection of references. Can be subclassed to apply checks or * store additional state when adding. */ static class ReferenceCollection implements Iterable<Reference> { List<Reference> references = Lists.newArrayList(); @Override public Iterator<Reference> iterator() { return references.iterator(); } void add(Reference reference) { references.add(reference); } /** * Determines if the variable for this reference collection is * "well-defined." A variable is well-defined if we can prove at * compile-time that it's assigned a value before it's used. * * Notice that if this function returns false, this doesn't imply that the * variable is used before it's assigned. It just means that we don't * have enough information to make a definitive judgment. */ protected boolean isWellDefined() { int size = references.size(); if (size == 0) { return false; } // If this is a declaration that does not instantiate the variable, // it's not well-defined. Reference init = getInitializingReference(); if (init == null) { return false; } Preconditions.checkState(references.get(0).isDeclaration()); BasicBlock initBlock = init.getBasicBlock(); for (int i = 1; i < size; i++) { if (!initBlock.provablyExecutesBefore( references.get(i).getBasicBlock())) { return false; } } return true; } /** * Whether the variable is escaped into an inner scope. */ boolean isEscaped() { Scope scope = null; for (Reference ref : references) { if (scope == null) { scope = ref.scope; } else if (scope != ref.scope) { return true; } } return false; } /** * @param index The index into the references array to look for an * assigning declaration. * * This is either the declaration if a value is assigned (such as * "var a =

Closure, 120

<FILEB>
<CHANGES>
if (ref.getSymbol().getScope()!= ref.scope) {
return false;
}
<CHANGEE>
<FILEE>
<FILEB> return references.get(i); } } return null; } /** * @return Whether the variable is only assigned a value once for its * lifetime. */ boolean isAssignedOnceInLifetime() { Reference ref = getOneAndOnlyAssignment(); if (ref == null) { return false; } // Make sure this assignment is not in a loop. for (BasicBlock block = ref.getBasicBlock(); block != null; block = block.getParent()) { if (block.isFunction) { <CHANGES> <CHANGEE> break; } else if (block.isLoop) { return false; } } return true; } /** * @return The one and only assignment. Returns if there are 0 or 2+ * assignments. */ private Reference getOneAndOnlyAssignment() { <FILEE> <SCANS>2", "function a()...", "... catch (a)..."). */ private boolean isInitializingDeclarationAt(int index) { Reference maybeInit = references.get(index); if (maybeInit.isInitializingDeclaration()) { // This is a declaration that represents the initial value. // Specifically, var declarations without assignments such as "var a;" // are not. return true; } return false; } /** * @param index The index into the references array to look for an * initialized assignment reference. That is, an assignment immediately * follow a variable declaration that itself does not initialize the * variable. */ private boolean isInitializingAssignmentAt(int index) { if (index < references.size() && index > 0) { Reference maybeDecl = references.get(index - 1); if (maybeDecl.isVarDeclaration()) { Preconditions.checkState(!maybeDecl.isInitializingDeclaration()); Reference maybeInit = references.get(index); if (maybeInit.isSimpleAssignmentToName()) { return true; } } } return false; } /** * @return The reference that provides the value for the variable at the * time of the first read, if known, otherwise null. * * This is either the variable declaration ("var a = ...") or first * reference following the declaration if it is an assignment. */ Reference getInitializingReference() { if (isInitializingDeclarationAt(0)) { return references.get(0); } else if (isInitializingAssignmentAt(1)) { return references.get(1); } return null; } /** * Constants are allowed to be defined after their first use. */ Reference getInitializingReferenceForConstants() { int size = references.size(); for (int i = 0; i < size; i++) { if (isInitializingDeclarationAt(i) || isInitializingAssignmentAt(i)) { Reference assignment = null; int size = references.size(); for (int i = 0; i < size; i++) { Reference ref = references.get(i); if (ref.isLvalue() || ref.isInitializingDeclaration()) { if (assignment == null) { assignment = ref; } else { return null; } } } return assignment; } /** * @return Whether the variable is never assigned a value. */ boolean isNeverAssigned() { int size = references.size(); for (int i = 0; i < size; i++) { Reference ref = references.get(i); if (ref.isLvalue() || ref.isInitializingDeclaration()) { return false; } } return true; } boolean firstReferenceIsAssigningDeclaration() { int size = references.size(); if (size > 0 && references.get(0).isInitializingDeclaration()) { return true; } return false; } } /** * Represents a single declaration or reference to a variable. */ static final class Reference implements StaticReference<JSType> { private static final Set<Integer> DECLARATION_PARENTS = ImmutableSet.of(Token.VAR, Token.FUNCTION, Token.CATCH); private final Node nameNode; private final BasicBlock basicBlock; private final Scope scope; private final InputId inputId; private final StaticSourceFile sourceFile; Reference(Node nameNode, NodeTraversal t, BasicBlock basicBlock) { this(nameNode, basicBlock, t.getScope(), t.getInput().getInputId()); } // Bleeding functions are weird, because the declaration does // not appear inside their scope. So they need

Closure, 120

<FILEB>
<CHANGES>
if (ref.getSymbol().getScope()!= ref.scope) {
return false;
}
<CHANGEE>
<FILEE>
<FILEB> return references.get(i); } } return null; } /** * @return Whether the variable is only assigned a value once for its * lifetime. */ boolean isAssignedOnceInLifetime() { Reference ref = getOneAndOnlyAssignment(); if (ref == null) { return false; } // Make sure this assignment is not in a loop. for (BasicBlock block = ref.getBasicBlock(); block != null; block = block.getParent()) { if (block.isFunction) { <CHANGES> <CHANGEE> break; } else if (block.isLoop) { return false; } } return true; } /** * @return The one and only assignment. Returns if there are 0 or 2+ * assignments. */ private Reference getOneAndOnlyAssignment() { <FILEE> <SCANS> their own constructor. static Reference newBleedingFunction(NodeTraversal t, BasicBlock basicBlock, Node func) { return new Reference(func.getFirstChild(), basicBlock, t.getScope(), t.getInput().getInputId()); } /** * Creates a variable reference in a given script file name, used in tests. * * @return The created reference. */ @VisibleForTesting static Reference createRefForTest(CompilerInput input) { return new Reference(new Node(Token.NAME), null, null, input.getInputId()); } private Reference(Node nameNode, BasicBlock basicBlock, Scope scope, InputId inputId) { this.nameNode = nameNode; this.basicBlock = basicBlock; this.scope = scope; this.inputId = inputId; this.sourceFile = nameNode.getStaticSourceFile(); } /** * Makes a copy of the current reference using a new Scope instance. */ Reference cloneWithNewScope(Scope newScope) { return new Reference(nameNode, basicBlock, newScope, inputId); } @Override public Var getSymbol() { return scope.getVar(nameNode.getString()); } @Override public Node getNode() { return nameNode; } public InputId getInputId() { return inputId; } @Override public StaticSourceFile getSourceFile() { return sourceFile; } boolean isDeclaration() { Node parent = getParent(); Node grandparent = parent.getParent(); return DECLARATION_PARENTS.contains(parent.getType()) || parent.isParamList() && grandparent.isFunction(); } boolean isVarDeclaration() { return getParent().isVar(); } boolean isHoistedFunction() { return NodeUtil.isHoistedFunctionDeclaration(getParent()); } /** * Determines whether the variable is initialized at the declaration. */ boolean isInitializingDeclaration() { // VAR is the only type of variable declaration that may not initialize // its variable. Catch blocks, named functions, and parameters all do. return isDeclaration() && !getParent().isVar() || nameNode.getFirstChild() != null; } /** * @return For an assignment, variable declaration, or function declaration * return the assigned value, otherwise null. */ Node getAssignedValue() { Node parent = getParent(); return (parent.isFunction()) ? parent : NodeUtil.getAssignedValue(nameNode); } BasicBlock getBasicBlock() { return basicBlock; } Node getParent() { return getNode().getParent(); } Node getGrandparent() { Node parent = getParent(); return parent == null ? null : parent.getParent(); } private static boolean isLhsOfForInExpression(Node n) { Node parent = n.getParent(); if (parent.isVar()) { return isLhsOfForInExpression(parent); } return NodeUtil.isForIn(parent) && parent.getFirstChild() == n; } boolean isSimpleAssignmentToName() { Node parent = getParent(); return parent.isAssign() && parent.getFirstChild() == nameNode; } boolean isLvalue() { Node parent = getParent(); int parentType = parent.getType(); return (parentType == Token.VAR && nameNode.getFirstChild() != null) || parentType == Token.INC || parentType == Token.DEC || (NodeUtil.isAssignmentOp(parent) && parent.getFirstChild() == nameNode) || isLhsOfForInExpression(nameNode); } Scope getScope() { return scope; } } /**

Closure, 120

<FILEB>
<CHANGES>
if (ref.getSymbol().getScope()!= ref.scope) {
return false;
}
<CHANGEE>
<FILEE>
<FILEB> return references.get(i); } } return null; } /** * @return Whether the variable is only assigned a value once for its * lifetime. */ boolean isAssignedOnceInLifetime() { Reference ref = getOneAndOnlyAssignment(); if (ref == null) { return false; } // Make sure this assignment is not in a loop. for (BasicBlock block = ref.getBasicBlock(); block != null; block = block.getParent()) { if (block.isFunction) { <CHANGES> <CHANGEE> break; } else if (block.isLoop) { return false; } } return true; } /** * @return The one and only assignment. Returns if there are 0 or 2+ * assignments. */ private Reference getOneAndOnlyAssignment() { <FILEE> <SCANS>/* * Copyright 2004 The Closure Compiler Authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.google.javascript.jscomp; import com.google.common.base.Preconditions; import com.google.javascript.jscomp.NodeTraversal.AbstractPostOrderCallback; import com.google.javascript.rhino.Node; import com.google.javascript.rhino.Token; import java.util.*; /** * Verifies that constants are only assigned a value once. * e.g. var XX = 5; * XX = 3; // error! * XX++; // error! * */ class ConstCheck extends AbstractPostOrderCallback implements CompilerPass { static final DiagnosticType CONST_REASSIGNED_VALUE_ERROR = DiagnosticType.error( "JSC_CONSTANT_REASSIGNED_VALUE_ERROR", "constant {0} assigned a value more than once"); private final AbstractCompiler compiler; private final Set<Scope.Var> initializedConstants; /** * Creates an instance. */ public ConstCheck(AbstractCompiler compiler) { this.compiler = compiler; this.initializedConstants = new HashSet<Scope.Var>(); } @Override public void process(Node externs, Node root) { Preconditions.checkState(compiler.getLifeCycleStage().isNormalized()); NodeTraversal.traverse(compiler, root, this); } @Override public void visit(NodeTraversal t, Node n, Node parent) { switch (n.getType()) { case Token.NAME: if (parent != null && parent.isVar() && n.hasChildren()) { String name = n.getString(); Scope.Var var = t.getScope().getVar(name); if (isConstant(var)) { if (initializedConstants.contains(var)) { reportError(t, n, name); } else { initializedConstants.add(var); } } } break; case Token.ASSIGN: case Token.ASSIGN_BITOR: case Token.ASSIGN_BITXOR: case Token.ASSIGN_BITAND: case Token.ASSIGN_LSH: case Token.ASSIGN_RSH: case Token.ASSIGN_URSH: case Token.ASSIGN_ADD: case Token.ASSIGN_SUB: case Token.ASSIGN_MUL: case Token.ASSIGN_DIV: case Token.ASSIGN_MOD: { Node lhs = n.getFirstChild(); if (lhs.isName()) { String name = lhs.getString(); Scope.Var var = t.getScope().getVar(name); if (isConstant(var)) { if (initializedConstants.contains(var)) { reportError(t, n, name); } else { initializedConstants.add(var); } } } break; } case Token.INC: case Token.DEC: { Node lhs = n.getFirstChild(); if (lhs.isName()) { String name = lhs.getString(); Scope.Var var = t.getScope().getVar(name); if (isConstant(var)) {

Closure, 120

<FILEB>
<CHANGES>
if (ref.getSymbol().getScope()!= ref.scope) {
return false;
}
<CHANGEE>
<FILEE>
<FILEB> return references.get(i); } } return null; } /** * @return Whether the variable is only assigned a value once for its * lifetime. */ boolean isAssignedOnceInLifetime() { Reference ref = getOneAndOnlyAssignment(); if (ref == null) { return false; } // Make sure this assignment is not in a loop. for (BasicBlock block = ref.getBasicBlock(); block != null; block = block.getParent()) { if (block.isFunction) { <CHANGES> <CHANGEE> break; } else if (block.isLoop) { return false; } } return true; } /** * @return The one and only assignment. Returns if there are 0 or 2+ * assignments. */ private Reference getOneAndOnlyAssignment() { <FILEE> <SCANS> * Filters variables declared as "constant", and declares them in the outer * declaredConstants map. * * In Google coding conventions, this means anything declared with @const * or named in all caps, and initialized to an immutable value. * CheckConsts has already verified that these are truly constants. */ private class IdentifyConstants implements Predicate<Var> { @Override public boolean apply(Var var) { return var.isConst(); } } /** * Filters non-global variables. */ private class IdentifyLocals implements Predicate<Var> { @Override public boolean apply(Var var) { return var.scope.isLocal(); } } private static class AliasCandidate { private final Var alias; private final ReferenceCollection refInfo; AliasCandidate(Var alias, ReferenceCollection refInfo) { this.alias = alias; this.refInfo = refInfo; } } /** * Builds up information about nodes in each scope. When exiting the * scope, inspects all variables in that scope, and inlines any * that we can. */ private class InliningBehavior implements Behavior { /** * A list of variables that should not be inlined, because their * reference information is out of sync with the state of the AST. */ private final Set<Var> staleVars = Sets.newHashSet(); /** * Stored possible aliases of variables that never change, with * all the reference info about those variables. Hashed by the NAME * node of the variable being aliased. */ final Map<Node, AliasCandidate> aliasCandidates = Maps.newHashMap(); @Override public void afterExitScope(NodeTraversal t, ReferenceMap referenceMap) { collectAliasCandidates(t, referenceMap); doInlinesForScope(t, referenceMap); } /** * If any of the variables are well-defined and alias other variables, * mark them as aliasing candidates. */ private void collectAliasCandidates(NodeTraversal t, ReferenceMap referenceMap) { if (mode != Mode.CONSTANTS_ONLY) { for (Iterator<Var> it = t.getScope().getVars(); it.hasNext();) { Var v = it.next(); ReferenceCollection referenceInfo = referenceMap.getReferences(v); // NOTE(nicksantos): Don't handle variables that are never used. // The tests are much easier to write if you don't, and there's // another pass that handles unused variables much more elegantly. if (referenceInfo != null && referenceInfo.references.size() >= 2 && referenceInfo.isWellDefined() && referenceInfo.isAssignedOnceInLifetime()) { Reference init = referenceInfo.getInitializingReference(); Node value = init.getAssignedValue(); if (value != null && value.isName()) { aliasCandidates.put(value, new AliasCandidate(v, referenceInfo)); } } } } } /** * For all variables in this scope, see if they are only used once. * If it looks safe to do so, inline them. */ private void doInlinesForScope(NodeTraversal t, ReferenceMap referenceMap) { boolean maybeModifiedArguments = maybeEscapedOrModifiedArguments(t.getScope(), referenceMap); for (Iterator<Var> it = t.getScope().getVars(); it.hasNext();) { Var v = it.next(); ReferenceCollection referenceInfo = referenceMap.getReferences(v); // referenceInfo will be null if we're in constants-only mode // and the variable is not a constant. if (referenceInfo == null || isVarInlineForbidden(v)) { // Never try to inline exported

Closure, 120

<FILEB>
<CHANGES>
if (ref.getSymbol().getScope()!= ref.scope) {
return false;
}
<CHANGEE>
<FILEE>
<FILEB> return references.get(i); } } return null; } /** * @return Whether the variable is only assigned a value once for its * lifetime. */ boolean isAssignedOnceInLifetime() { Reference ref = getOneAndOnlyAssignment(); if (ref == null) { return false; } // Make sure this assignment is not in a loop. for (BasicBlock block = ref.getBasicBlock(); block != null; block = block.getParent()) { if (block.isFunction) { <CHANGES> <CHANGEE> break; } else if (block.isLoop) { return false; } } return true; } /** * @return The one and only assignment. Returns if there are 0 or 2+ * assignments. */ private Reference getOneAndOnlyAssignment() { <FILEE> <SCANS> variables or variables that // were not collected or variables that have already been inlined. continue; } else if (isInlineableDeclaredConstant(v, referenceInfo)) { Reference init = referenceInfo.getInitializingReferenceForConstants(); Node value = init.getAssignedValue(); inlineDeclaredConstant(v, value, referenceInfo.references); staleVars.add(v); } else if (mode == Mode.CONSTANTS_ONLY) { // If we're in constants-only mode, don't run more aggressive // inlining heuristics. See InlineConstantsTest. continue; } else { inlineNonConstants(v, referenceInfo, maybeModifiedArguments); } } } private boolean maybeEscapedOrModifiedArguments( Scope scope, ReferenceMap referenceMap) { if (scope.isLocal()) { Var arguments = scope.getArgumentsVar(); ReferenceCollection refs = referenceMap.getReferences(arguments); if (refs != null && !refs.references.isEmpty()) { for (Reference ref : refs.references) { Node refNode = ref.getNode(); Node refParent = ref.getParent(); // Any reference that is not a read of the arguments property // consider a escape of the arguments object. if (!(NodeUtil.isGet(refParent) && refNode == ref.getParent().getFirstChild() && !isLValue(refParent))) { return true; } } } } return false; } private boolean isLValue(Node n) { Node parent = n.getParent(); return (parent.isInc() || parent.isDec() || (NodeUtil.isAssignmentOp(parent) && parent.getFirstChild() == n)); } private void inlineNonConstants( Var v, ReferenceCollection referenceInfo, boolean maybeModifiedArguments) { int refCount = referenceInfo.references.size(); Reference declaration = referenceInfo.references.get(0); Reference init = referenceInfo.getInitializingReference(); int firstRefAfterInit = (declaration == init) ? 2 : 3; if (refCount > 1 && isImmutableAndWellDefinedVariable(v, referenceInfo)) { // if the variable is referenced more than once, we can only // inline it if it's immutable and never defined before referenced. Node value; if (init != null) { value = init.getAssignedValue(); } else { // Create a new node for variable that is never initialized. Node srcLocation = declaration.getNode(); value = NodeUtil.newUndefinedNode(srcLocation); } Preconditions.checkNotNull(value); inlineWellDefinedVariable(v, value, referenceInfo.references); staleVars.add(v); } else if (refCount == firstRefAfterInit) { // The variable likely only read once, try some more // complex inlining heuristics. Reference reference = referenceInfo.references.get( firstRefAfterInit - 1); if (canInline(declaration, init, reference)) { inline(v, declaration, init, reference); staleVars.add(v); } } else if (declaration != init && refCount == 2) { if (isValidDeclaration(declaration) && isValidInitialization(init)) { // The only reference is the initialization, remove the assignment and // the variable declaration. Node value = init.getAssignedValue(); Preconditions.checkNotNull(value); inlineWellDefinedVariable(v, value, referenceInfo.references); staleVars.add(v); } } // If this variable was not inlined normally, check if we can // inline an alias of it. (If the variable was inlined, then the // reference data is out

Closure, 120

<FILEB>
<CHANGES>
if (ref.getSymbol().getScope()!= ref.scope) {
return false;
}
<CHANGEE>
<FILEE>
<FILEB> return references.get(i); } } return null; } /** * @return Whether the variable is only assigned a value once for its * lifetime. */ boolean isAssignedOnceInLifetime() { Reference ref = getOneAndOnlyAssignment(); if (ref == null) { return false; } // Make sure this assignment is not in a loop. for (BasicBlock block = ref.getBasicBlock(); block != null; block = block.getParent()) { if (block.isFunction) { <CHANGES> <CHANGEE> break; } else if (block.isLoop) { return false; } } return true; } /** * @return The one and only assignment. Returns if there are 0 or 2+ * assignments. */ private Reference getOneAndOnlyAssignment() { <FILEE> <SCANS> of sync. We're better off just waiting for // the next pass.) if (!maybeModifiedArguments && !staleVars.contains(v) && referenceInfo.isWellDefined() && referenceInfo.isAssignedOnceInLifetime()) { List<Reference> refs = referenceInfo.references; for (int i = 1 /* start from a read */; i < refs.size(); i++) { Node nameNode = refs.get(i).getNode(); if (aliasCandidates.containsKey(nameNode)) { AliasCandidate candidate = aliasCandidates.get(nameNode); if (!staleVars.contains(candidate.alias) && !isVarInlineForbidden(candidate.alias)) { Reference aliasInit; aliasInit = candidate.refInfo.getInitializingReference(); Node value = aliasInit.getAssignedValue(); Preconditions.checkNotNull(value); inlineWellDefinedVariable(candidate.alias, value, candidate.refInfo.references); staleVars.add(candidate.alias); } } } } } /** * If there are any variable references in the given node tree, blacklist * them to prevent the pass from trying to inline the variable. */ private void blacklistVarReferencesInTree(Node root, Scope scope) { for (Node c = root.getFirstChild(); c != null; c = c.getNext()) { blacklistVarReferencesInTree(c, scope); } if (root.isName()) { staleVars.add(scope.getVar(root.getString())); } } /** * Whether the given variable is forbidden from being inlined. */ private boolean isVarInlineForbidden(Var var) { // A variable may not be inlined if: // 1) The variable is exported, // 2) A reference to the variable has been inlined. We're downstream // of the mechanism that creates variable references, so we don't // have a good way to update the reference. Just punt on it. // 3) Don't inline the special RENAME_PROPERTY_FUNCTION_NAME return var.isExtern() || compiler.getCodingConvention().isExported(var.name) || RenameProperties.RENAME_PROPERTY_FUNCTION_NAME.equals(var.name) || staleVars.contains(var); } /** * Do the actual work of inlining a single declaration into a single * reference. */ private void inline(Var v, Reference decl, Reference init, Reference ref) { Node value = init.getAssignedValue(); Preconditions.checkState(value != null); // Check for function declarations before the value is moved in the AST. boolean isFunctionDeclaration = NodeUtil.isFunctionDeclaration(value); compiler.reportChangeToEnclosingScope(ref.getNode()); inlineValue(v, ref, value.detachFromParent()); if (decl != init) { Node expressRoot = init.getGrandparent(); Preconditions.checkState(expressRoot.isExprResult()); NodeUtil.removeChild(expressRoot.getParent(), expressRoot); } // Function declarations have already been removed. if (!isFunctionDeclaration) { compiler.reportChangeToEnclosingScope(decl.getNode()); removeDeclaration(decl); } } /** * Inline an immutable variable into all of its references. */ private void inlineWellDefinedVariable(Var v, Node value, List<Reference> refSet) { Reference decl = refSet.get(0); for (int i = 1; i < refSet.size(); i++) { inlineValue(v, refSet.get(i), value.cloneTree()); } removeDeclaration(decl); } /** * Inline a declared constant. */

Closure, 120

<FILEB>
<CHANGES>
if (ref.getSymbol().getScope()!= ref.scope) {
return false;
}
<CHANGEE>
<FILEE>
<FILEB> return references.get(i); } } return null; } /** * @return Whether the variable is only assigned a value once for its * lifetime. */ boolean isAssignedOnceInLifetime() { Reference ref = getOneAndOnlyAssignment(); if (ref == null) { return false; } // Make sure this assignment is not in a loop. for (BasicBlock block = ref.getBasicBlock(); block != null; block = block.getParent()) { if (block.isFunction) { <CHANGES> <CHANGEE> break; } else if (block.isLoop) { return false; } } return true; } /** * @return The one and only assignment. Returns if there are 0 or 2+ * assignments. */ private Reference getOneAndOnlyAssignment() { <FILEE> <SCANS> private void inlineDeclaredConstant(Var v, Node value, List<Reference> refSet) { // Replace the references with the constant value Reference decl = null; for (Reference r : refSet) { if (r.getNode() == v.getNameNode()) { decl = r; } else { inlineValue(v, r, value.cloneTree()); } } removeDeclaration(decl); } /** * Remove the given VAR declaration. */ private void removeDeclaration(Reference decl) { Node varNode = decl.getParent(); Node grandparent = decl.getGrandparent(); compiler.reportChangeToEnclosingScope(decl.getNode()); varNode.removeChild(decl.getNode()); // Remove var node if empty if (!varNode.hasChildren()) { Preconditions.checkState(varNode.isVar()); NodeUtil.removeChild(grandparent, varNode); } } /** * Replace the given reference with the given value node. * * @param v The variable that's referenced. * @param ref The reference to replace. * @param value The node tree to replace it with. This tree should be safe * to re-parent. */ private void inlineValue(Var v, Reference ref, Node value) { compiler.reportChangeToEnclosingScope(ref.getNode()); if (ref.isSimpleAssignmentToName()) { // This is the initial assignment. ref.getGrandparent().replaceChild(ref.getParent(), value); } else { ref.getParent().replaceChild(ref.getNode(), value); } blacklistVarReferencesInTree(value, v.scope); } /** * Determines whether the given variable is declared as a constant * and may be inlined. */ private boolean isInlineableDeclaredConstant(Var var, ReferenceCollection refInfo) { if (!identifyConstants.apply(var)) { return false; } if (!refInfo.isAssignedOnceInLifetime()) { return false; } Reference init = refInfo.getInitializingReferenceForConstants(); if (init == null) { return false; } Node value = init.getAssignedValue(); if (value == null) { // This constant is either externally defined or initialized indirectly // (e.g. in an function expression used to hide // temporary variables), so the constant is ineligible for inlining. return false; } // Is the constant's value immutable? if (!NodeUtil.isImmutableValue(value)) { return false; } // Determine if we should really inline a String or not. return !value.isString() || isStringWorthInlining(var, refInfo.references); } /** * Compute whether the given string is worth inlining. */ private boolean isStringWorthInlining(Var var, List<Reference> refs) { if (!inlineAllStrings && !var.isDefine()) { int len = var.getInitialValue().getString().length() + "''".length(); // if not inlined: var xx="value"; .. xx .. xx .. // The 4 bytes per reference is just a heuristic: // 2 bytes per var name plus maybe 2 bytes if we don't inline, e.g. // in the case of "foo " + CONST + " bar" int noInlineBytes = "var xx=;".length() + len + 4 * (refs.size() - 1); // if inlined: // I'm going to assume that half of the quotes will be eliminated // thanks to constant folding, therefore I subtract 1 (2/2=1) from // the string length. int

Closure, 120

<FILEB>
<CHANGES>
if (ref.getSymbol().getScope()!= ref.scope) {
return false;
}
<CHANGEE>
<FILEE>
<FILEB> return references.get(i); } } return null; } /** * @return Whether the variable is only assigned a value once for its * lifetime. */ boolean isAssignedOnceInLifetime() { Reference ref = getOneAndOnlyAssignment(); if (ref == null) { return false; } // Make sure this assignment is not in a loop. for (BasicBlock block = ref.getBasicBlock(); block != null; block = block.getParent()) { if (block.isFunction) { <CHANGES> <CHANGEE> break; } else if (block.isLoop) { return false; } } return true; } /** * @return The one and only assignment. Returns if there are 0 or 2+ * assignments. */ private Reference getOneAndOnlyAssignment() { <FILEE> <SCANS>ConstructorsCallback implements Callback { private final List<String> constructors = Lists.newArrayList(); private final List<String> requires = Lists.newArrayList(); private final List<Node> newNodes = Lists.newArrayList(); @Override public boolean shouldTraverse(NodeTraversal t, Node n, Node parent) { return parent == null || !parent.isScript() || !t.getInput().isExtern(); } @Override public void visit(NodeTraversal t, Node n, Node parent) { switch (n.getType()) { case Token.ASSIGN: case Token.VAR: maybeAddConstructor(t, n); break; case Token.FUNCTION: // Exclude function expressions. if (NodeUtil.isStatement(n)) { maybeAddConstructor(t, n); } break; case Token.CALL: visitCallNode(n, parent); break; case Token.SCRIPT: visitScriptNode(t); break; case Token.NEW: visitNewNode(t, n); } } private void visitScriptNode(NodeTraversal t) { Set<String> classNames = Sets.newHashSet(); for (Node node : newNodes) { String className = node.getFirstChild().getQualifiedName(); String outermostClassName = getOutermostClassName(className); boolean notProvidedByConstructors = (constructors == null || !constructors.contains(className)); boolean notProvidedByRequires = (requires == null || (!requires.contains(className) && !requires.contains(outermostClassName))); if (notProvidedByConstructors && notProvidedByRequires && !classNames.contains(className)) { compiler.report( t.makeError(node, level, MISSING_REQUIRE_WARNING, className)); classNames.add(className); } } // for the next script, if there is one, we don't want the new, ctor, and // require nodes to spill over. this.newNodes.clear(); this.requires.clear(); this.constructors.clear(); } private void visitCallNode(Node n, Node parent) { String required = codingConvention.extractClassNameIfRequire(n, parent); if (required != null) { requires.add(required); } } private void visitNewNode(NodeTraversal t, Node n) { Node qNameNode = n.getFirstChild(); // If the ctor is something other than a qualified name, ignore it. if (!qNameNode.isQualifiedName()) { return; } // Grab the root ctor namespace. Node nameNode = qNameNode; for (; nameNode.hasChildren(); nameNode = nameNode.getFirstChild()) {} // We only consider programmer-defined constructors that are // global variables, or are defined on global variables. if (!nameNode.isName()) { return; } String name = nameNode.getString(); Scope.Var var = t.getScope().getVar(name); if (var == null || var.isLocal() || var.isExtern()) { return; } newNodes.add(n); } private void maybeAddConstructor(NodeTraversal t, Node n) { JSDocInfo info = (JSDocInfo) n.getProp(Node.JSDOC_INFO_PROP); if (info != null) { String ctorName = n.getFirstChild().getQualifiedName(); if (info.isConstructor()) { constructors.add(ctorName); } else { JSTypeExpression typeExpr = info.getType(); if (typeExpr != null) { JSType type = typeExpr.evaluate(t.getScope(), compiler.getTypeRegistry

Closure, 120

<FILEB>
<CHANGES>
if (ref.getSymbol().getScope()!= ref.scope) {
return false;
}
<CHANGEE>
<FILEE>
<FILEB> return references.get(i); } } return null; } /** * @return Whether the variable is only assigned a value once for its * lifetime. */ boolean isAssignedOnceInLifetime() { Reference ref = getOneAndOnlyAssignment(); if (ref == null) { return false; } // Make sure this assignment is not in a loop. for (BasicBlock block = ref.getBasicBlock(); block != null; block = block.getParent()) { if (block.isFunction) { <CHANGES> <CHANGEE> break; } else if (block.isLoop) { return false; } } return true; } /** * @return The one and only assignment. Returns if there are 0 or 2+ * assignments. */ private Reference getOneAndOnlyAssignment() { <FILEE> <SCANS> node, Node parent) { return nextConvention.extractClassNameIfRequire(node, parent); } @Override public String getExportPropertyFunction() { return nextConvention.getExportPropertyFunction(); } @Override public String getExportSymbolFunction() { return nextConvention.getExportSymbolFunction(); } @Override public List<String> identifyTypeDeclarationCall(Node n) { return nextConvention.identifyTypeDeclarationCall(n); } @Override public void applySubclassRelationship(FunctionType parentCtor, FunctionType childCtor, SubclassType type) { nextConvention.applySubclassRelationship( parentCtor, childCtor, type); } @Override public String getAbstractMethodName() { return nextConvention.getAbstractMethodName(); } @Override public String getSingletonGetterClassName(Node callNode) { return nextConvention.getSingletonGetterClassName(callNode); } @Override public void applySingletonGetter(FunctionType functionType, FunctionType getterType, ObjectType objectType) { nextConvention.applySingletonGetter( functionType, getterType, objectType); } @Override public boolean isInlinableFunction(Node n) { return nextConvention.isInlinableFunction(n); } @Override public DelegateRelationship getDelegateRelationship(Node callNode) { return nextConvention.getDelegateRelationship(callNode); } @Override public void applyDelegateRelationship( ObjectType delegateSuperclass, ObjectType delegateBase, ObjectType delegator, FunctionType delegateProxy, FunctionType findDelegate) { nextConvention.applyDelegateRelationship( delegateSuperclass, delegateBase, delegator, delegateProxy, findDelegate); } @Override public String getDelegateSuperclassName() { return nextConvention.getDelegateSuperclassName(); } @Override public void checkForCallingConventionDefiningCalls( Node n, Map<String, String> delegateCallingConventions) { nextConvention.checkForCallingConventionDefiningCalls( n, delegateCallingConventions); } @Override public void defineDelegateProxyPrototypeProperties( JSTypeRegistry registry, StaticScope<JSType> scope, List<ObjectType> delegateProxyPrototypes, Map<String, String> delegateCallingConventions) { nextConvention.defineDelegateProxyPrototypeProperties( registry, scope, delegateProxyPrototypes, delegateCallingConventions); } @Override public String getGlobalObject() { return nextConvention.getGlobalObject(); } @Override public Collection<AssertionFunctionSpec> getAssertionFunctions() { return nextConvention.getAssertionFunctions(); } @Override public Bind describeFunctionBind(Node n) { return describeFunctionBind(n, false); } @Override public Bind describeFunctionBind(Node n, boolean useTypeInfo) { return nextConvention.describeFunctionBind(n, useTypeInfo); } @Override public boolean isPropertyTestFunction(Node call) { return nextConvention.isPropertyTestFunction(call); } @Override public boolean isPrototypeAlias(Node getProp) { return false; } @Override public ObjectLiteralCast getObjectLiteralCast(Node callNode) { return nextConvention.getObjectLiteralCast(callNode); } @Override public Collection<String> getIndirectlyDeclaredProperties() { return nextConvention.getIndirectlyDeclaredProperties(); } } /** * The default coding convention. * Should be at the bottom of all proxy chains. */ private static class DefaultCodingConvention implements CodingConvention { private static final long serialVersionUID = 1L; @Override public boolean isConstant(String variableName) { return false; } @Override public boolean isConstantKey(String variableName) { return false; }

Closure, 120

<FILEB>
<CHANGES>
if (ref.getSymbol().getScope()!= ref.scope) {
return false;
}
<CHANGEE>
<FILEE>
<FILEB> return references.get(i); } } return null; } /** * @return Whether the variable is only assigned a value once for its * lifetime. */ boolean isAssignedOnceInLifetime() { Reference ref = getOneAndOnlyAssignment(); if (ref == null) { return false; } // Make sure this assignment is not in a loop. for (BasicBlock block = ref.getBasicBlock(); block != null; block = block.getParent()) { if (block.isFunction) { <CHANGES> <CHANGEE> break; } else if (block.isLoop) { return false; } } return true; } /** * @return The one and only assignment. Returns if there are 0 or 2+ * assignments. */ private Reference getOneAndOnlyAssignment() { <FILEE> <SCANS> @Override public boolean isValidEnumKey(String key) { return key != null && key.length() > 0; } @Override public boolean isOptionalParameter(Node parameter) { // be as lax as possible, but this must be mutually exclusive from // var_args parameters. return false; } @Override public boolean isVarArgsParameter(Node parameter) { // be as lax as possible return false; } @Override public boolean isExported(String name, boolean local) { return local && name.startsWith("$super"); } @Override public boolean isExported(String name) { return isExported(name, false) || isExported(name, true); } @Override public boolean isPrivate(String name) { return false; } @Override public SubclassRelationship getClassesDefinedByCall(Node callNode) { return null; } @Override public boolean isSuperClassReference(String propertyName) { return false; } @Override public String extractClassNameIfProvide(Node node, Node parent) { String message = "only implemented in GoogleCodingConvention"; throw new UnsupportedOperationException(message); } @Override public String extractClassNameIfRequire(Node node, Node parent) { String message = "only implemented in GoogleCodingConvention"; throw new UnsupportedOperationException(message); } @Override public String getExportPropertyFunction() { return null; } @Override public String getExportSymbolFunction() { return null; } @Override public List<String> identifyTypeDeclarationCall(Node n) { return null; } @Override public void applySubclassRelationship(FunctionType parentCtor, FunctionType childCtor, SubclassType type) { // do nothing } @Override public String getAbstractMethodName() { return null; } @Override public String getSingletonGetterClassName(Node callNode) { return null; } @Override public void applySingletonGetter(FunctionType functionType, FunctionType getterType, ObjectType objectType) { // do nothing. } @Override public boolean isInlinableFunction(Node n) { Preconditions.checkState(n.isFunction()); return true; } @Override public DelegateRelationship getDelegateRelationship(Node callNode) { return null; } @Override public void applyDelegateRelationship( ObjectType delegateSuperclass, ObjectType delegateBase, ObjectType delegator, FunctionType delegateProxy, FunctionType findDelegate) { // do nothing. } @Override public String getDelegateSuperclassName() { return null; } @Override public void checkForCallingConventionDefiningCalls(Node n, Map<String, String> delegateCallingConventions) { // do nothing. } @Override public void defineDelegateProxyPrototypeProperties( JSTypeRegistry registry, StaticScope<JSType> scope, List<ObjectType> delegateProxyPrototypes, Map<String, String> delegateCallingConventions) { // do nothing. } @Override public String getGlobalObject() { return "window"; } @Override public boolean isPropertyTestFunction(Node call) { return false; } @Override public boolean isPrototypeAlias(Node getProp) { return false; } @Override public ObjectLiteralCast getObjectLiteralCast(Node callNode) { return null; } @Override public Collection<AssertionFunctionSpec> getAssertionFunctions() { return Collections.emptySet(); } @Override public Bind describeFunctionBind(Node n) { return describeFunctionBind(n, false); } @Override public Bind describeFunctionBind(Node n, boolean useTypeInfo) {

Closure, 120

<FILEB>
<CHANGES>
if (ref.getSymbol().getScope()!= ref.scope) {
return false;
}
<CHANGEE>
<FILEE>
<FILEB> return references.get(i); } } return null; } /** * @return Whether the variable is only assigned a value once for its * lifetime. */ boolean isAssignedOnceInLifetime() { Reference ref = getOneAndOnlyAssignment(); if (ref == null) { return false; } // Make sure this assignment is not in a loop. for (BasicBlock block = ref.getBasicBlock(); block != null; block = block.getParent()) { if (block.isFunction) { <CHANGES> <CHANGEE> break; } else if (block.isLoop) { return false; } } return true; } /** * @return The one and only assignment. Returns if there are 0 or 2+ * assignments. */ private Reference getOneAndOnlyAssignment() { <FILEE> <SCANS> { JSType returnType = explicitReturnExpected(t.getScopeRoot()); if (returnType == null) { return; } if (fastAllPathsReturnCheck(t.getControlFlowGraph())) { return; } CheckPathsBetweenNodes<Node, ControlFlowGraph.Branch> test = new CheckPathsBetweenNodes<Node, ControlFlowGraph.Branch>( t.getControlFlowGraph(), t.getControlFlowGraph().getEntry(), t.getControlFlowGraph().getImplicitReturn(), IS_RETURN, GOES_THROUGH_TRUE_CONDITION_PREDICATE); if (!test.allPathsSatisfyPredicate()) { compiler.report( t.makeError(t.getScopeRoot(), level, MISSING_RETURN_STATEMENT, returnType.toString())); } } /** * Fast check to see if all execution paths contain a return statement. * May spuriously report that a return statement is missing. * * @return true if all paths return, converse not necessarily true */ private static boolean fastAllPathsReturnCheck(ControlFlowGraph<Node> cfg) { for (DiGraphEdge<Node, Branch> s : cfg.getImplicitReturn().getInEdges()) { if (!s.getSource().getValue().isReturn()) { return false; } } return true; } @Override public void exitScope(NodeTraversal t) { } @Override public boolean shouldTraverse( NodeTraversal nodeTraversal, Node n, Node parent) { return true; } @Override public void visit(NodeTraversal t, Node n, Node parent) { } /** * Determines if the given scope should explicitly return. All functions * with non-void or non-unknown return types must have explicit returns. * * Exception: Constructors which specifically specify a return type are * used to allow invocation without requiring the "new" keyword. They * have an implicit return type. See unit tests. * * @return If a return type is expected, returns it. Otherwise, returns null. */ private JSType explicitReturnExpected(Node scope) { FunctionType scopeType = JSType.toMaybeFunctionType(scope.getJSType()); if (scopeType == null) { return null; } if (isEmptyFunction(scope)) { return null; } if (scopeType.isConstructor()) { return null; } JSType returnType = scopeType.getReturnType(); if (returnType == null) { return null; } if (!isVoidOrUnknown(returnType)) { return returnType; } return null; } /** * @return {@code true} if function represents a JavaScript function * with an empty body */ private static boolean isEmptyFunction(Node function) { return function.getChildCount() == 3 && !function.getFirstChild().getNext().getNext().hasChildren(); } /** * @return {@code true} if returnType is void, unknown, or a union * containing void or unknown */ private boolean isVoidOrUnknown(JSType returnType) { final JSType voidType = compiler.getTypeRegistry().getNativeType(JSTypeNative.VOID_TYPE); return voidType.isSubtype(returnType); } }

Closure, 120

<FILEB>
<CHANGES>
if (ref.getSymbol().getScope()!= ref.scope) {
return false;
}
<CHANGEE>
<FILEE>
<FILEB> return references.get(i); } } return null; } /** * @return Whether the variable is only assigned a value once for its * lifetime. */ boolean isAssignedOnceInLifetime() { Reference ref = getOneAndOnlyAssignment(); if (ref == null) { return false; } // Make sure this assignment is not in a loop. for (BasicBlock block = ref.getBasicBlock(); block != null; block = block.getParent()) { if (block.isFunction) { <CHANGES> <CHANGEE> break; } else if (block.isLoop) { return false; } } return true; } /** * @return The one and only assignment. Returns if there are 0 or 2+ * assignments. */ private Reference getOneAndOnlyAssignment() { <FILEE> <SCANS>param var The variable that we're checking. * @param variableName The name of the variable. * @param newType The type being applied to the variable. Mostly just here * for the benefit of the warning. * @return The variable we end up with. Most of the time, this will just * be {@code var}, but in some rare cases we will need to declare * a new var with new source info. */ Var expectUndeclaredVariable(String sourceName, CompilerInput input, Node n, Node parent, Var var, String variableName, JSType newType) { Var newVar = var; boolean allowDupe = false; if (n.isGetProp() || NodeUtil.isObjectLitKey(n)) { JSDocInfo info = n.getJSDocInfo(); if (info == null) { info = parent.getJSDocInfo(); } allowDupe = info != null && info.getSuppressions().contains("duplicate"); } JSType varType = var.getType(); // Only report duplicate declarations that have types. Other duplicates // will be reported by the syntactic scope creator later in the // compilation process. if (varType != null && varType != typeRegistry.getNativeType(UNKNOWN_TYPE) && newType != null && newType != typeRegistry.getNativeType(UNKNOWN_TYPE)) { // If there are two typed declarations of the same variable, that // is an error and the second declaration is ignored, except in the // case of native types. A null input type means that the declaration // was made in TypedScopeCreator#createInitialScope and is a // native type. We should redeclare it at the new input site. if (var.input == null) { Scope s = var.getScope(); s.undeclare(var); newVar = s.declare(variableName, n, varType, input, false); n.setJSType(varType); if (parent.isVar()) { if (n.getFirstChild() != null) { n.getFirstChild().setJSType(varType); } } else { Preconditions.checkState(parent.isFunction()); parent.setJSType(varType); } } else { // Always warn about duplicates if the overridden type does not // match the original type. // // If the types match, suppress the warning iff there was a @suppress // tag, or if the original declaration was a stub. if (!(allowDupe || var.getParentNode().isExprResult()) || !newType.isEquivalentTo(varType)) { report(JSError.make(sourceName, n, DUP_VAR_DECLARATION, variableName, newType.toString(), var.getInputName(), String.valueOf(var.nameNode.getLineno()), varType.toString())); } } } return newVar; } /** * Expect that all properties on interfaces that this type implements are * implemented and correctly typed. */ void expectAllInterfaceProperties(NodeTraversal t, Node n, FunctionType type) { ObjectType instance = type.getInstanceType(); for (ObjectType implemented : type.getAllImplementedInterfaces()) { if (implemented.getImplicitPrototype() != null) { for (String prop : implemented.getImplicitPrototype().getOwnPropertyNames()) { expectInterfaceProperty(t, n, instance, implemented, prop); } } } } /** * Expect that the property in an interface that this type implements is * implemented and correctly typed. */ private void expectInterfaceProperty(NodeTraversal t, Node n, ObjectType instance, ObjectType implementedInterface

Closure, 120

<FILEB>
<CHANGES>
if (ref.getSymbol().getScope()!= ref.scope) {
return false;
}
<CHANGEE>
<FILEE>
<FILEB> return references.get(i); } } return null; } /** * @return Whether the variable is only assigned a value once for its * lifetime. */ boolean isAssignedOnceInLifetime() { Reference ref = getOneAndOnlyAssignment(); if (ref == null) { return false; } // Make sure this assignment is not in a loop. for (BasicBlock block = ref.getBasicBlock(); block != null; block = block.getParent()) { if (block.isFunction) { <CHANGES> <CHANGEE> break; } else if (block.isLoop) { return false; } } return true; } /** * @return The one and only assignment. Returns if there are 0 or 2+ * assignments. */ private Reference getOneAndOnlyAssignment() { <FILEE> <SCANS> Preconditions.checkState(s.isGlobal()); inputId = null; sourceName = ""; curNode = root; pushScope(s); traverseBranch(root, null); popScope(); } /** * Traverses a parse tree recursively with a scope, starting at that scope's * root. */ void traverseAtScope(Scope s) { Node n = s.getRootNode(); if (n.isFunction()) { // We need to do some extra magic to make sure that the scope doesn't // get re-created when we dive into the function. if (inputId == null) { inputId = NodeUtil.getInputId(n); } sourceName = getSourceName(n); curNode = n; pushScope(s); Node args = n.getFirstChild().getNext(); Node body = args.getNext(); traverseBranch(args, n); traverseBranch(body, n); popScope(); } else { traverseWithScope(n, s); } } /** * Traverses an inner node recursively with a refined scope. An inner node may * be any node with a non {@code null} parent (i.e. all nodes except the * root). * * @param node the node to traverse * @param parent the node's parent, it may not be {@code null} * @param refinedScope the refined scope of the scope currently at the top of * the scope stack or in trivial cases that very scope or {@code null} */ protected void traverseInnerNode(Node node, Node parent, Scope refinedScope) { Preconditions.checkNotNull(parent); if (refinedScope != null && getScope() != refinedScope) { curNode = node; pushScope(refinedScope); traverseBranch(node, parent); popScope(); } else { traverseBranch(node, parent); } } public AbstractCompiler getCompiler() { return compiler; } /** * Gets the current line number, or zero if it cannot be determined. The line * number is retrieved lazily as a running time optimization. */ public int getLineNumber() { Node cur = curNode; while (cur != null) { int line = cur.getLineno(); if (line >= 0) { return line; } cur = cur.getParent(); } return 0; } /** * Gets the current input source name. * * @return A string that may be empty, but not null */ public String getSourceName() { return sourceName; } /** * Gets the current input source. */ public CompilerInput getInput() { return compiler.getInput(inputId); } /** * Gets the current input module. */ public JSModule getModule() { CompilerInput input = getInput(); return input == null ? null : input.getModule(); } /** Returns the node currently being traversed. */ public Node getCurrentNode() { return curNode; } /** * Traversal for passes that work only on changed functions. * Suppose a loopable pass P1 uses this traversal. * Then, if a function doesn't change between two runs of P1, it won't look at * the function the second time. * (We're assuming that P1 runs to a fixpoint, o/w we may miss optimizations.) * * Most changes are reported with calls to Compiler.reportCodeChange(), which * doesn't know which scope changed. We keep track of the current scope by * calling Compiler.setScope inside pushScope and popScope. * The automatic tracking can be wrong in

Closure, 120

<FILEB>
<CHANGES>
if (ref.getSymbol().getScope()!= ref.scope) {
return false;
}
<CHANGEE>
<FILEE>
<FILEB> return references.get(i); } } return null; } /** * @return Whether the variable is only assigned a value once for its * lifetime. */ boolean isAssignedOnceInLifetime() { Reference ref = getOneAndOnlyAssignment(); if (ref == null) { return false; } // Make sure this assignment is not in a loop. for (BasicBlock block = ref.getBasicBlock(); block != null; block = block.getParent()) { if (block.isFunction) { <CHANGES> <CHANGEE> break; } else if (block.isLoop) { return false; } } return true; } /** * @return The one and only assignment. Returns if there are 0 or 2+ * assignments. */ private Reference getOneAndOnlyAssignment() { <FILEE> <SCANS> { return null; } else { if (scopeRoots.isEmpty()) { return scopes.peek().getRootNode(); } else { return scopeRoots.peek(); } } } /** Creates a new scope (e.g. when entering a function). */ private void pushScope(Node node) { Preconditions.checkState(curNode != null); compiler.setScope(node); scopeRoots.push(node); cfgs.push(null); if (scopeCallback != null) { scopeCallback.enterScope(this); } } /** Creates a new scope (e.g. when entering a function). */ private void pushScope(Scope s) { Preconditions.checkState(curNode != null); compiler.setScope(s.getRootNode()); scopes.push(s); cfgs.push(null); if (scopeCallback != null) { scopeCallback.enterScope(this); } } /** Pops back to the previous scope (e.g. when leaving a function). */ private void popScope() { if (scopeCallback != null) { scopeCallback.exitScope(this); } if (scopeRoots.isEmpty()) { scopes.pop(); } else { scopeRoots.pop(); } cfgs.pop(); if (hasScope()) { compiler.setScope(getScopeRoot()); } } /** Gets the current scope. */ public Scope getScope() { Scope scope = scopes.isEmpty() ? null : scopes.peek(); if (scopeRoots.isEmpty()) { return scope; } Iterator<Node> it = scopeRoots.descendingIterator(); while (it.hasNext()) { scope = scopeCreator.createScope(it.next(), scope); scopes.push(scope); } scopeRoots.clear(); // No need to call compiler.setScope; the top scopeRoot is now the top scope return scope; } /** Gets the control flow graph for the current JS scope. */ public ControlFlowGraph<Node> getControlFlowGraph() { if (cfgs.peek() == null) { ControlFlowAnalysis cfa = new ControlFlowAnalysis(compiler, false, true); cfa.process(null, getScopeRoot()); cfgs.pop(); cfgs.push(cfa.getCfg()); } return cfgs.peek(); } /** Returns the current scope's root. */ public Node getScopeRoot() { if (scopeRoots.isEmpty()) { return scopes.peek().getRootNode(); } else { return scopeRoots.peek(); } } /** * Determines whether the traversal is currently in the global scope. */ boolean inGlobalScope() { return getScopeDepth() <= 1; } int getScopeDepth() { return scopes.size() + scopeRoots.size(); } public boolean hasScope() { return !(scopes.isEmpty() && scopeRoots.isEmpty()); } /** Reports a diagnostic (error or warning) */ public void report(Node n, DiagnosticType diagnosticType, String... arguments) { JSError error = JSError.make( getBestSourceFileName(n), n, diagnosticType, arguments); compiler.report(error); } private static String getSourceName(Node n) { String name = n.getSourceFileName(); return name == null ? "" : name; } InputId getInputId() { return inputId; } /** * Creates a JSError during NodeTraversal. * * @param n Determines the line and char position within the source file name * @param type The DiagnosticType * @param arguments Arguments to be incorporated into the message

Closure, 120

<FILEB>
<CHANGES>
if (ref.getSymbol().getScope()!= ref.scope) {
return false;
}
<CHANGEE>
<FILEE>
<FILEB> return references.get(i); } } return null; } /** * @return Whether the variable is only assigned a value once for its * lifetime. */ boolean isAssignedOnceInLifetime() { Reference ref = getOneAndOnlyAssignment(); if (ref == null) { return false; } // Make sure this assignment is not in a loop. for (BasicBlock block = ref.getBasicBlock(); block != null; block = block.getParent()) { if (block.isFunction) { <CHANGES> <CHANGEE> break; } else if (block.isLoop) { return false; } } return true; } /** * @return The one and only assignment. Returns if there are 0 or 2+ * assignments. */ private Reference getOneAndOnlyAssignment() { <FILEE> <SCANS> } else if (n.isGetProp() && n.getParent().isExprResult() && isMarkedExpose(n)) { exposedProperties.add(n.getLastChild().getString()); } } private boolean isMarkedExpose(Node n) { JSDocInfo info = n.getJSDocInfo(); return info != null && info.isExpose(); } } /** * Rewrite all exposed properties in [] form. */ private class RewriteExposedProperties extends AbstractPostOrderCallback { private final Set<String> exposedProperties; RewriteExposedProperties(Set<String> exposedProperties) { this.exposedProperties = exposedProperties; } @Override public void visit(NodeTraversal t, Node n, Node parent) { if (n.isGetProp()) { String propName = n.getLastChild().getString(); if (exposedProperties.contains(propName)) { Node obj = n.removeFirstChild(); Node prop = n.removeFirstChild(); n.getParent().replaceChild(n, IR.getelem(obj, prop)); compiler.reportCodeChange(); } } else if (n.isStringKey()) { String propName = n.getString(); if (exposedProperties.contains(propName)) { n.setQuotedString(); compiler.reportCodeChange(); } } } } /** * Propagate constant annotations over the Var graph. */ static class PropagateConstantAnnotationsOverVars extends AbstractPostOrderCallback implements CompilerPass { private final AbstractCompiler compiler; private final boolean assertOnChange; PropagateConstantAnnotationsOverVars( AbstractCompiler compiler, boolean forbidChanges) { this.compiler = compiler; this.assertOnChange = forbidChanges; } @Override public void process(Node externs, Node root) { new NodeTraversal(compiler, this).traverseRoots(externs, root); } @Override public void visit(NodeTraversal t, Node n, Node parent) { // Note: Constant properties annotations are not propagated. if (n.isName()) { if (n.getString().isEmpty()) { return; } JSDocInfo info = null; // Find the JSDocInfo for a top-level variable. Var var = t.getScope().getVar(n.getString()); if (var != null) { info = var.getJSDocInfo(); } boolean shouldBeConstant = (info != null && info.isConstant()) || NodeUtil.isConstantByConvention( compiler.getCodingConvention(), n, parent); boolean isMarkedConstant = n.getBooleanProp(Node.IS_CONSTANT_NAME); if (shouldBeConstant && !isMarkedConstant) { if (assertOnChange) { String name = n.getString(); throw new IllegalStateException( "Unexpected const change.\n" + " name: "+ name + "\n" + " parent:" + n.getParent().toStringTree()); } n.putBooleanProp(Node.IS_CONSTANT_NAME, true); } } } } /** * Walk the AST tree and verify that constant names are used consistently. */ static class VerifyConstants extends AbstractPostOrderCallback implements CompilerPass { final private AbstractCompiler compiler; final private boolean checkUserDeclarations; VerifyConstants(AbstractCompiler compiler, boolean checkUserDeclarations) { this.compiler = compiler; this.checkUserDeclarations = checkUserDeclarations; } @Override public void process(Node externs, Node root) { Node externsAndJs = root.getParent(); Preconditions.checkState(externsAndJs != null); Preconditions.checkState(externsAnd

Closure, 120

<FILEB>
<CHANGES>
if (ref.getSymbol().getScope()!= ref.scope) {
return false;
}
<CHANGEE>
<FILEE>
<FILEB> return references.get(i); } } return null; } /** * @return Whether the variable is only assigned a value once for its * lifetime. */ boolean isAssignedOnceInLifetime() { Reference ref = getOneAndOnlyAssignment(); if (ref == null) { return false; } // Make sure this assignment is not in a loop. for (BasicBlock block = ref.getBasicBlock(); block != null; block = block.getParent()) { if (block.isFunction) { <CHANGES> <CHANGEE> break; } else if (block.isLoop) { return false; } } return true; } /** * @return The one and only assignment. Returns if there are 0 or 2+ * assignments. */ private Reference getOneAndOnlyAssignment() { <FILEE> <SCANS>Js.hasChild(externs)); NodeTraversal.traverseRoots( compiler, Lists.newArrayList(externs, root), this); } private Map<String, Boolean> constantMap = Maps.newHashMap(); @Override public void visit(NodeTraversal t, Node n, Node parent) { if (n.isName()) { String name = n.getString(); if (n.getString().isEmpty()) { return; } boolean isConst = n.getBooleanProp(Node.IS_CONSTANT_NAME); if (checkUserDeclarations) { boolean expectedConst = false; CodingConvention convention = compiler.getCodingConvention(); if (NodeUtil.isConstantName(n) || NodeUtil.isConstantByConvention(convention, n, parent)) { expectedConst = true; } else { expectedConst = false; JSDocInfo info = null; Var var = t.getScope().getVar(n.getString()); if (var != null) { info = var.getJSDocInfo(); } if (info != null && info.isConstant()) { expectedConst = true; } else { expectedConst = false; } } if (expectedConst) { Preconditions.checkState(expectedConst == isConst, "The name %s is not annotated as constant.", name); } else { Preconditions.checkState(expectedConst == isConst, "The name %s should not be annotated as constant.", name); } } Boolean value = constantMap.get(name); if (value == null) { constantMap.put(name, isConst); } else { Preconditions.checkState(value.booleanValue() == isConst, "The name %s is not consistently annotated as constant.", name); } } } } /** * Simplify the AST: * - VAR declarations split, so they represent exactly one child * declaration. * - WHILEs are converted to FORs * - FOR loop are initializers are moved out of the FOR structure * - LABEL node of children other than LABEL, BLOCK, WHILE, FOR, or DO are * moved into a block. * - Add constant annotations based on coding convention. */ static class NormalizeStatements implements Callback { private final AbstractCompiler compiler; private final boolean assertOnChange; NormalizeStatements(AbstractCompiler compiler, boolean assertOnChange) { this.compiler = compiler; this.assertOnChange = assertOnChange; } private void reportCodeChange(String changeDescription) { if (assertOnChange) { throw new IllegalStateException( "Normalize constraints violated:\n" + changeDescription); } compiler.reportCodeChange(); } @Override public boolean shouldTraverse(NodeTraversal t, Node n, Node parent) { doStatementNormalizations(n); return true; } @Override public void visit(NodeTraversal t, Node n, Node parent) { switch (n.getType()) { case Token.WHILE: if (CONVERT_WHILE_TO_FOR) { Node expr = n.getFirstChild(); n.setType(Token.FOR); Node empty = IR.empty(); empty.copyInformationFrom(n); n.addChildBefore(empty, expr); n.addChildAfter(empty.cloneNode(), expr); reportCodeChange("WHILE node"); } break; case Token.FUNCTION: normalizeFunctionDeclaration(n); break; case Token.NAME: case Token.STRING: case Token.STRING_KEY: case Token.GETTER_DEF: case Token.SETTER_DEF: if (!compiler.getLifeCycleStage().isNormalizedObfuscated

Closure, 120

<FILEB>
<CHANGES>
if (ref.getSymbol().getScope()!= ref.scope) {
return false;
}
<CHANGEE>
<FILEE>
<FILEB> return references.get(i); } } return null; } /** * @return Whether the variable is only assigned a value once for its * lifetime. */ boolean isAssignedOnceInLifetime() { Reference ref = getOneAndOnlyAssignment(); if (ref == null) { return false; } // Make sure this assignment is not in a loop. for (BasicBlock block = ref.getBasicBlock(); block != null; block = block.getParent()) { if (block.isFunction) { <CHANGES> <CHANGEE> break; } else if (block.isLoop) { return false; } } return true; } /** * @return The one and only assignment. Returns if there are 0 or 2+ * assignments. */ private Reference getOneAndOnlyAssignment() { <FILEE> <SCANS>.isLabel()); // We should never get here. LABELs with a single VAR statement should // already have been normalized to have a BLOCK. throw new IllegalStateException("Unexpected LABEL"); } } reportCodeChange("Duplicate VAR declaration"); } } /** * A simple class that causes scope to be created. */ private final class ScopeTicklingCallback implements NodeTraversal.ScopedCallback { @Override public void enterScope(NodeTraversal t) { // Cause the scope to be created, which will cause duplicate // to be found. t.getScope(); } @Override public void exitScope(NodeTraversal t) { // Nothing to do. } @Override public boolean shouldTraverse( NodeTraversal nodeTraversal, Node n, Node parent) { return true; } @Override public void visit(NodeTraversal t, Node n, Node parent) { // Nothing to do. } } }

Closure, 120

<FILEB>
<CHANGES>
if (ref.getSymbol().getScope()!= ref.scope) {
return false;
}
<CHANGEE>
<FILEE>
<FILEB> return references.get(i); } } return null; } /** * @return Whether the variable is only assigned a value once for its * lifetime. */ boolean isAssignedOnceInLifetime() { Reference ref = getOneAndOnlyAssignment(); if (ref == null) { return false; } // Make sure this assignment is not in a loop. for (BasicBlock block = ref.getBasicBlock(); block != null; block = block.getParent()) { if (block.isFunction) { <CHANGES> <CHANGEE> break; } else if (block.isLoop) { return false; } } return true; } /** * @return The one and only assignment. Returns if there are 0 or 2+ * assignments. */ private Reference getOneAndOnlyAssignment() { <FILEE> <SCANS>/* * * ***** BEGIN LICENSE BLOCK ***** * Version: MPL 1.1/GPL 2.0 * * The contents of this file are subject to the Mozilla Public License Version * 1.1 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * http://www.mozilla.org/MPL/ * * Software distributed under the License is distributed on an "AS IS" basis, * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License * for the specific language governing rights and limitations under the * License. * * The Original Code is Rhino code, released * May 6, 1999. * * The Initial Developer of the Original Code is * Netscape Communications Corporation. * Portions created by the Initial Developer are Copyright (C) 1997-1999 * the Initial Developer. All Rights Reserved. * * Contributor(s): * Nick Santos * * Alternatively, the contents of this file may be used under the terms of * the GNU General Public License Version 2 or later (the "GPL"), in which * case the provisions of the GPL are applicable instead of those above. If * you wish to allow use of your version of this file only under the terms of * the GPL and not to allow others to use your version of this file under the * MPL, indicate your decision by deleting the provisions above and replacing * them with the notice and other provisions required by the GPL. If you do * not delete the provisions above, a recipient may use your version of this * file under either the MPL or the GPL. * * ***** END LICENSE BLOCK ***** */ package com.google.javascript.rhino.jstype; import com.google.javascript.rhino.Node; /** * The {@code StaticReference} tells us all the ways that a {@code StaticSlot} * is used in a program. * * @author nicksantos@google.com (Nick Santos) */ public interface StaticReference<T> { /** * The variable that this reference points to. */ StaticSlot<T> getSymbol(); /** * The node where the reference lives. */ Node getNode(); /** * The source file where the reference lives. */ StaticSourceFile getSourceFile(); }

Closure, 120

<FILEB>
<CHANGES>
if (ref.getSymbol().getScope()!= ref.scope) {
return false;
}
<CHANGEE>
<FILEE>
<FILEB> return references.get(i); } } return null; } /** * @return Whether the variable is only assigned a value once for its * lifetime. */ boolean isAssignedOnceInLifetime() { Reference ref = getOneAndOnlyAssignment(); if (ref == null) { return false; } // Make sure this assignment is not in a loop. for (BasicBlock block = ref.getBasicBlock(); block != null; block = block.getParent()) { if (block.isFunction) { <CHANGES> <CHANGEE> break; } else if (block.isLoop) { return false; } } return true; } /** * @return The one and only assignment. Returns if there are 0 or 2+ * assignments. */ private Reference getOneAndOnlyAssignment() { <FILEE> <SCANS> } @Override public ObjectType getImplicitPrototype() { return null; } @Override public FunctionType getConstructor() { return null; } @Override public String getReferenceName() { return isChecked ? "??" : "?"; } @Override public String getDisplayName() { return "Unknown"; } @Override public boolean hasDisplayName() { return true; } @Override public BooleanLiteralSet getPossibleToBooleanOutcomes() { return BooleanLiteralSet.BOTH; } @Override JSType resolveInternal(ErrorReporter t, StaticScope<JSType> scope) { return this; } }

Closure, 120

<FILEB>
<CHANGES>
if (ref.getSymbol().getScope()!= ref.scope) {
return false;
}
<CHANGEE>
<FILEE>
<FILEB> return references.get(i); } } return null; } /** * @return Whether the variable is only assigned a value once for its * lifetime. */ boolean isAssignedOnceInLifetime() { Reference ref = getOneAndOnlyAssignment(); if (ref == null) { return false; } // Make sure this assignment is not in a loop. for (BasicBlock block = ref.getBasicBlock(); block != null; block = block.getParent()) { if (block.isFunction) { <CHANGES> <CHANGEE> break; } else if (block.isLoop) { return false; } } return true; } /** * @return The one and only assignment. Returns if there are 0 or 2+ * assignments. */ private Reference getOneAndOnlyAssignment() { <FILEE> <SCANS>; this.sanityCheck = sanityCheck; } @Override public void process(Node externs, Node root) { // Don't run externs-checking in sanity check mode. Normalization will // remove duplicate VAR declarations, which will make // externs look like they have assigns. if (!sanityCheck) { NodeTraversal.traverse(compiler, externs, new NameRefInExternsCheck()); } NodeTraversal.traverseRoots( compiler, Lists.newArrayList(externs, root), this); for (String varName : varsToDeclareInExterns) { createSynthesizedExternVar(varName); } } @Override public void hotSwapScript(Node scriptRoot, Node originalRoot) { Preconditions.checkState(scriptRoot.isScript()); NodeTraversal t = new NodeTraversal(compiler, this); // Note we use the global scope to prevent wrong "undefined-var errors" on // variables that are defined in other JS files. t.traverseWithScope(scriptRoot, SyntacticScopeCreator.generateUntypedTopScope(compiler)); // TODO(bashir) Check if we need to createSynthesizedExternVar like process. } @Override public void visit(NodeTraversal t, Node n, Node parent) { if (!n.isName()) { return; } String varName = n.getString(); // Only a function can have an empty name. if (varName.isEmpty()) { Preconditions.checkState(parent.isFunction()); Preconditions.checkState(NodeUtil.isFunctionExpression(parent)); return; } // Check if this is a declaration for a var that has been declared // elsewhere. If so, mark it as a duplicate. if ((parent.isVar() || NodeUtil.isFunctionDeclaration(parent)) && varsToDeclareInExterns.contains(varName)) { createSynthesizedExternVar(varName); n.addSuppression("duplicate"); } // Check that the var has been declared. Scope scope = t.getScope(); Scope.Var var = scope.getVar(varName); if (var == null) { if (NodeUtil.isFunctionExpression(parent)) { // e.g. [ function foo() {} ], it's okay if "foo" isn't defined in the // current scope. } else { // The extern checks are stricter, don't report a second error. if (!strictExternCheck || !t.getInput().isExtern()) { t.report(n, UNDEFINED_VAR_ERROR, varName); } if (sanityCheck) { throw new IllegalStateException("Unexpected variable " + varName); } else { createSynthesizedExternVar(varName); scope.getGlobalScope().declare(varName, n, null, getSynthesizedExternsInput()); } } return; } CompilerInput currInput = t.getInput(); CompilerInput varInput = var.input; if (currInput == varInput || currInput == null || varInput == null) { // The variable was defined in the same file. This is fine. return; } // Check module dependencies. JSModule currModule = currInput.getModule(); JSModule varModule = varInput.getModule(); JSModuleGraph moduleGraph = compiler.getModuleGraph(); if (!sanityCheck && varModule != currModule && varModule != null && currModule != null) { if (moduleGraph.dependsOn(currModule, varModule)) { // The module dependency was properly declared. } else { if (scope.isGlobal()) { if (moduleGraph.dependsOn(varModule, currModule)) { // The variable reference violates

Closure, 120

<FILEB>
<CHANGES>
if (ref.getSymbol().getScope()!= ref.scope) {
return false;
}
<CHANGEE>
<FILEE>
<FILEB> return references.get(i); } } return null; } /** * @return Whether the variable is only assigned a value once for its * lifetime. */ boolean isAssignedOnceInLifetime() { Reference ref = getOneAndOnlyAssignment(); if (ref == null) { return false; } // Make sure this assignment is not in a loop. for (BasicBlock block = ref.getBasicBlock(); block != null; block = block.getParent()) { if (block.isFunction) { <CHANGES> <CHANGEE> break; } else if (block.isLoop) { return false; } } return true; } /** * @return The one and only assignment. Returns if there are 0 or 2+ * assignments. */ private Reference getOneAndOnlyAssignment() { <FILEE> <SCANS> a declared module dependency. t.report(n, VIOLATED_MODULE_DEP_ERROR, currModule.getName(), varModule.getName(), varName); } else { // The variable reference is between two modules that have no // dependency relationship. This should probably be considered an // error, but just issue a warning for now. t.report(n, MISSING_MODULE_DEP_ERROR, currModule.getName(), varModule.getName(), varName); } } else { t.report(n, STRICT_MODULE_DEP_ERROR, currModule.getName(), varModule.getName(), varName); } } } } /** * Create a new variable in a synthetic script. This will prevent * subsequent compiler passes from crashing. */ private void createSynthesizedExternVar(String varName) { Node nameNode = IR.name(varName); // Mark the variable as constant if it matches the coding convention // for constant vars. // NOTE(nicksantos): honestly, I'm not sure how much this matters. // AFAIK, all people who use the CONST coding convention also // compile with undeclaredVars as errors. We have some test // cases for this configuration though, and it makes them happier. if (compiler.getCodingConvention().isConstant(varName)) { nameNode.putBooleanProp(Node.IS_CONSTANT_NAME, true); } getSynthesizedExternsRoot().addChildToBack( IR.var(nameNode)); varsToDeclareInExterns.remove(varName); compiler.reportCodeChange(); } /** * A check for name references in the externs inputs. These used to prevent * a variable from getting renamed, but no longer have any effect. */ private class NameRefInExternsCheck extends AbstractPostOrderCallback { @Override public void visit(NodeTraversal t, Node n, Node parent) { if (n.isName()) { switch (parent.getType()) { case Token.VAR: case Token.FUNCTION: case Token.PARAM_LIST: // These are okay. break; case Token.GETPROP: if (n == parent.getFirstChild()) { Scope scope = t.getScope(); Scope.Var var = scope.getVar(n.getString()); if (var == null) { t.report(n, UNDEFINED_EXTERN_VAR_ERROR, n.getString()); varsToDeclareInExterns.add(n.getString()); } } break; default: t.report(n, NAME_REFERENCE_IN_EXTERNS_ERROR, n.getString()); Scope scope = t.getScope(); Scope.Var var = scope.getVar(n.getString()); if (var == null) { varsToDeclareInExterns.add(n.getString()); } break; } } } } /** Lazily create a "new" externs input for undeclared variables. */ private CompilerInput getSynthesizedExternsInput() { return compiler.getSynthesizedExternsInput(); } /** Lazily create a "new" externs root for undeclared variables. */ private Node getSynthesizedExternsRoot() { if (synthesizedExternsRoot == null) { CompilerInput synthesizedExterns = getSynthesizedExternsInput(); synthesizedExternsRoot = synthesizedExterns.getAstRoot(compiler); } return synthesizedExternsRoot; } }

Closure, 120

<FILEB>
<CHANGES>
if (ref.getSymbol().getScope()!= ref.scope) {
return false;
}
<CHANGEE>
<FILEE>
<FILEB> return references.get(i); } } return null; } /** * @return Whether the variable is only assigned a value once for its * lifetime. */ boolean isAssignedOnceInLifetime() { Reference ref = getOneAndOnlyAssignment(); if (ref == null) { return false; } // Make sure this assignment is not in a loop. for (BasicBlock block = ref.getBasicBlock(); block != null; block = block.getParent()) { if (block.isFunction) { <CHANGES> <CHANGEE> break; } else if (block.isLoop) { return false; } } return true; } /** * @return The one and only assignment. Returns if there are 0 or 2+ * assignments. */ private Reference getOneAndOnlyAssignment() { <FILEE> <SCANS> thatType */ private static boolean isPossiblySubtype(JSType thisType, JSType thatType) { if (thisType == null) { return false; } JSType type = thisType; if (type.isUnionType()) { for (JSType alternate : type.toMaybeUnionType().getAlternates()) { if (alternate.isSubtype(thatType)) { return true; } } } else { if (type.isSubtype(thatType)) { return true; } } return false; } private static JSType dereference(JSType type) { return type == null ? null : type.dereference(); } /* * Create a unique identification string for Node n, or null if function * called with invalid argument. * * This function is basically used to distinguish between: * A.B = function() { * this.eh = new ... * } * and * C.D = function() { * this.eh = new ... * } * * As well as * A.B = function() { * var eh = new ... * } * and * C.D = function() { * var eh = new ... * } * * Warning: Inheritance is not currently handled. */ private static String generateKey(NodeTraversal t, Node n, boolean noLocalVariables) { if (n == null) { return null; } String key; Node scopeNode = t.getScopeRoot(); if (n.isName()) { if (noLocalVariables) { return null; } key = n.getQualifiedName(); if (scopeNode.isFunction()) { JSType parentScopeType = t.getScope().getParentScope().getTypeOfThis(); /* * If the locally defined variable is defined within a function, use * the function name to create ID. */ if (!parentScopeType.isGlobalThisType()) { key = parentScopeType.toString() + "~" + key; } key = NodeUtil.getFunctionName(scopeNode) + "=" + key; } } else { /* * Only handle cases such as a.b.c.X and not cases where the * eventful object is stored in an array or uses a function to * determine the index. * * Note: Inheritance changes the name that should be returned here */ if (!n.isQualifiedName()) { return null; } key = n.getQualifiedName(); /* * If it is not a simple variable and doesn't use this, then we assume * global variable. */ Node base = getBase(n); if (base != null && base.isThis()) { if (base.getJSType().isUnknownType()) { // Handle anonymous function created in constructor: // // /** // * @extends {goog.SubDisposable} // * @constructor */ // speel.Person = function() { // this.run = function() { // this.eh = new goog.events.EventHandler(); // } //}; key = t.getScope().getParentScope().getTypeOfThis().toString() + "~" + key; } else { if (n.getFirstChild() == null) { key = base.getJSType().toString() + "=" + key; } else { ObjectType objectType = ObjectType.cast(dereference(n.getFirstChild().getJSType())); if (objectType == null) { return null; } ObjectType hObjT = objectType; String propertyName = n

Closure, 120

<FILEB>
<CHANGES>
if (ref.getSymbol().getScope()!= ref.scope) {
return false;
}
<CHANGEE>
<FILEE>
<FILEB> return references.get(i); } } return null; } /** * @return Whether the variable is only assigned a value once for its * lifetime. */ boolean isAssignedOnceInLifetime() { Reference ref = getOneAndOnlyAssignment(); if (ref == null) { return false; } // Make sure this assignment is not in a loop. for (BasicBlock block = ref.getBasicBlock(); block != null; block = block.getParent()) { if (block.isFunction) { <CHANGES> <CHANGEE> break; } else if (block.isLoop) { return false; } } return true; } /** * @return The one and only assignment. Returns if there are 0 or 2+ * assignments. */ private Reference getOneAndOnlyAssignment() { <FILEE> <SCANS>Type)) { return true; } return false; } /* * Log that thisType eventizes thatType. */ private void addEventize(JSType thisType, JSType thatType) { if (collectorFilterType(thisType) || collectorFilterType(thatType) || thisType.isEquivalentTo(thatType)) { return; } String className = thisType.getDisplayName(); if (thatType.isUnionType()) { UnionType ut = thatType.toMaybeUnionType(); for (JSType type : ut.getAlternates()) { if (type.isObject()) { addEventizeClass(className, type); } } } else { addEventizeClass(className, thatType); } } private void addEventizeClass(String className, JSType thatType) { String propertyJsTypeName = thatType.getDisplayName(); Set<String> eventize = eventizes.get(propertyJsTypeName); if (eventize == null) { eventize = new HashSet<String>(); eventizes.put(propertyJsTypeName, eventize); } eventize.add(className); } @Override public void enterScope(NodeTraversal t) { Node n = t.getScopeRoot(); boolean isConstructor = false; boolean isInDisposal = false; String functionName = null; /* * Scope entered is a function definition */ if (n.isFunction()) { functionName = NodeUtil.getFunctionName(n); /* * Skip anonymous functions */ if (functionName != null) { JSDocInfo jsDocInfo = NodeUtil.getBestJSDocInfo(n); if (jsDocInfo != null) { /* * Record constructor of a type */ if (jsDocInfo.isConstructor()) { isConstructor = true; /* * Initialize eventizes relationship */ if (t.getScope() != null && t.getScope().getTypeOfThis() != null) { ObjectType objectType = ObjectType.cast(t.getScope() .getTypeOfThis().dereference()); /* * Eventize due to inheritance */ while (objectType != null) { objectType = objectType.getImplicitPrototype(); if (objectType == null) { break; } if (objectType.getDisplayName().endsWith("prototype")) { continue; } addEventize(compiler.getTypeRegistry().getType(functionName), objectType); /* * Don't add transitive eventize edges here, it will be * taken care of in computeEventful */ break; } } } } /* * Indicate within a disposeInternal member */ if (functionName.endsWith(".disposeInternal")) { isInDisposal = true; } } isConstructorStack.push(isConstructor); isDisposalStack.push(isInDisposal); } else { isConstructorStack.push(inConstructorScope()); isDisposalStack.push(inDisposalScope()); } } @Override public void exitScope(NodeTraversal t) { isConstructorStack.pop(); isDisposalStack.pop(); } /* * Is the current node a call to goog.events.unlisten */ private void isGoogEventsUnlisten(Node n) { Preconditions.checkArgument(n.getChildCount() > 3); Node listener = n.getChildAtIndex(3); Node objectWithListener = n.getChildAtIndex(1); if (!objectWithListener.isQualifiedName()) { return; } if (listener.isFunction()) { /* * Anonymous function */

Closure, 120

<FILEB>
<CHANGES>
if (ref.getSymbol().getScope()!= ref.scope) {
return false;
}
<CHANGEE>
<FILEE>
<FILEB> return references.get(i); } } return null; } /** * @return Whether the variable is only assigned a value once for its * lifetime. */ boolean isAssignedOnceInLifetime() { Reference ref = getOneAndOnlyAssignment(); if (ref == null) { return false; } // Make sure this assignment is not in a loop. for (BasicBlock block = ref.getBasicBlock(); block != null; block = block.getParent()) { if (block.isFunction) { <CHANGES> <CHANGEE> break; } else if (block.isLoop) { return false; } } return true; } /** * @return The one and only assignment. Returns if there are 0 or 2+ * assignments. */ private Reference getOneAndOnlyAssignment() { <FILEE> <SCANS> compiler.report(JSError.make(n.getSourceFileName(), n, UNLISTEN_WITH_ANONBOUND)); } else if (listener.isCall()) { if (!listener.getFirstChild().isQualifiedName()) { /* * Anonymous function */ compiler.report(JSError.make(n.getSourceFileName(), n, UNLISTEN_WITH_ANONBOUND)); } else if (listener.getFirstChild().getQualifiedName() .equals("goog.bind")) { /* * Using goog.bind to unlisten */ compiler.report(JSError.make(n.getSourceFileName(), n, UNLISTEN_WITH_ANONBOUND)); } } } private void visitCall(NodeTraversal t, Node n) { Node functionCalled = n.getFirstChild(); if (functionCalled == null || !functionCalled.isQualifiedName()) { return; } String functionCalledName = functionCalled.getQualifiedName(); JSType typeOfThis = getTypeOfThisForScope(t); if (typeOfThis == null) { return; } /* * Class considered eventful if there is an unlisten call in the * disposal. */ if (functionCalledName.equals("goog.events.unlisten")) { if (inDisposalScope()) { eventfulTypes.add(typeOfThis); } isGoogEventsUnlisten(n); } if (inDisposalScope() && functionCalledName.equals("goog.events.removeAll")) { eventfulTypes.add(typeOfThis); } /* * If member with qualified name gets disposed of when this class * gets disposed, consider the member type as an eventizer of this * class. */ JSType disposedType = maybeReturnDisposedType(n, inDisposalScope()); if (!collectorFilterType(disposedType)) { addEventize(getTypeOfThisForScope(t), disposedType); } } @Override public void visit(NodeTraversal t, Node n, Node parent) { switch (n.getType()) { case Token.CALL: visitCall(t, n); break; default: break; } } } private class Traversal extends AbstractPostOrderCallback implements ScopedCallback { /* * Checks if the input node correspond to the creation of an eventful object */ private boolean createsEventfulObject(Node n) { Node first = n.getFirstChild(); JSType type = n.getJSType(); if (first == null || !first.isQualifiedName() || type.isEmptyType() || type.isUnknownType()) { return false; } boolean isOfTypeNeedingDisposal = false; for (JSType disposableType : eventfulTypes) { if (type.isSubtype(disposableType)) { isOfTypeNeedingDisposal = true; break; } } return isOfTypeNeedingDisposal; } /* * This function traverses the current scope to see if a locally * defined eventful object is assigned to a live-out variable. * * Note: This function could be called multiple times to traverse * the same scope if multiple local eventful objects are created in the * scope. */ private Node localEventfulObjectAssign( NodeTraversal t, Node propertyNode) { Node parent; if (!t.getScope().isGlobal()) { /* * In function */ parent = NodeUtil.getFunctionBody(t.getScopeRoot()); } else { /* * In global scope */ parent = t.getScopeRoot().getFirstChild(); } /* * Check to see if locally

Closure, 120

<FILEB>
<CHANGES>
if (ref.getSymbol().getScope()!= ref.scope) {
return false;
}
<CHANGEE>
<FILEE>
<FILEB> return references.get(i); } } return null; } /** * @return Whether the variable is only assigned a value once for its * lifetime. */ boolean isAssignedOnceInLifetime() { Reference ref = getOneAndOnlyAssignment(); if (ref == null) { return false; } // Make sure this assignment is not in a loop. for (BasicBlock block = ref.getBasicBlock(); block != null; block = block.getParent()) { if (block.isFunction) { <CHANGES> <CHANGEE> break; } else if (block.isLoop) { return false; } } return true; } /** * @return The one and only assignment. Returns if there are 0 or 2+ * assignments. */ private Reference getOneAndOnlyAssignment() { <FILEE> <SCANS> @Override public void enterScope(NodeTraversal t) { /* * Local variables captured in scope are filtered at present. * LiveVariableAnalysis used to filter such variables. */ ControlFlowGraph<Node> cfg = t.getControlFlowGraph(); LiveVariablesAnalysis liveness = new LiveVariablesAnalysis(cfg, t.getScope(), compiler); liveness.analyze(); for (Var v : liveness.getEscapedLocals()) { eventfulObjectDisposed(t, v.getNode()); } } @Override public void exitScope(NodeTraversal t) { } @Override public void visit(NodeTraversal t, Node n, Node parent) { switch (n.getType()) { case Token.ASSIGN: visitAssign(t, n); break; case Token.CALL: visitCall(t, n); break; case Token.FUNCTION: visitFunction(t, n); break; case Token.NEW: visitNew(t, n, parent); break; case Token.RETURN: visitReturn(t, n); break; default: break; } } } }

Closure, 120

<FILEB>
<CHANGES>
if (ref.getSymbol().getScope()!= ref.scope) {
return false;
}
<CHANGEE>
<FILEE>
<FILEB> return references.get(i); } } return null; } /** * @return Whether the variable is only assigned a value once for its * lifetime. */ boolean isAssignedOnceInLifetime() { Reference ref = getOneAndOnlyAssignment(); if (ref == null) { return false; } // Make sure this assignment is not in a loop. for (BasicBlock block = ref.getBasicBlock(); block != null; block = block.getParent()) { if (block.isFunction) { <CHANGES> <CHANGEE> break; } else if (block.isLoop) { return false; } } return true; } /** * @return The one and only assignment. Returns if there are 0 or 2+ * assignments. */ private Reference getOneAndOnlyAssignment() { <FILEE> <SCANS>Compiler compiler, ReverseAbstractInterpreter reverseInterpreter, JSTypeRegistry typeRegistry, CheckLevel reportMissingOverride) { this(compiler, reverseInterpreter, typeRegistry, null, null, reportMissingOverride); } TypeCheck(AbstractCompiler compiler, ReverseAbstractInterpreter reverseInterpreter, JSTypeRegistry typeRegistry) { this(compiler, reverseInterpreter, typeRegistry, null, null, CheckLevel.WARNING); } /** Turn on the missing property check. Returns this for easy chaining. */ TypeCheck reportMissingProperties(boolean report) { reportMissingProperties = report; return this; } /** * Main entry point for this phase of processing. This follows the pattern for * JSCompiler phases. * * @param externsRoot The root of the externs parse tree. * @param jsRoot The root of the input parse tree to be checked. */ @Override public void process(Node externsRoot, Node jsRoot) { Preconditions.checkNotNull(scopeCreator); Preconditions.checkNotNull(topScope); Node externsAndJs = jsRoot.getParent(); Preconditions.checkState(externsAndJs != null); Preconditions.checkState( externsRoot == null || externsAndJs.hasChild(externsRoot)); if (externsRoot != null) { check(externsRoot, true); } check(jsRoot, false); } /** Main entry point of this phase for testing code. */ public Scope processForTesting(Node externsRoot, Node jsRoot) { Preconditions.checkState(scopeCreator == null); Preconditions.checkState(topScope == null); Preconditions.checkState(jsRoot.getParent() != null); Node externsAndJsRoot = jsRoot.getParent(); scopeCreator = new MemoizedScopeCreator(new TypedScopeCreator(compiler)); topScope = scopeCreator.createScope(externsAndJsRoot, null); TypeInferencePass inference = new TypeInferencePass(compiler, reverseInterpreter, topScope, scopeCreator); inference.process(externsRoot, jsRoot); process(externsRoot, jsRoot); return topScope; } public void check(Node node, boolean externs) { Preconditions.checkNotNull(node); NodeTraversal t = new NodeTraversal(compiler, this, scopeCreator); inExterns = externs; t.traverseWithScope(node, topScope); if (externs) { inferJSDocInfo.process(node, null); } else { inferJSDocInfo.process(null, node); } } private void checkNoTypeCheckSection(Node n, boolean enterSection) { switch (n.getType()) { case Token.SCRIPT: case Token.BLOCK: case Token.VAR: case Token.FUNCTION: case Token.ASSIGN: JSDocInfo info = n.getJSDocInfo(); if (info != null && info.isNoTypeCheck()) { if (enterSection) { noTypeCheckSection++; } else { noTypeCheckSection--; } } validator.setShouldReport(noTypeCheckSection == 0); break; } } private void report(NodeTraversal t, Node n, DiagnosticType diagnosticType, String... arguments) { if (noTypeCheckSection == 0) { t.report(n, diagnosticType, arguments); } } @Override public boolean shouldTraverse( NodeTraversal t, Node n, Node parent) { checkNoTypeCheckSection(n, true); switch (n.getType()) { case Token.FUNCTION: // normal type checking final Scope outerScope = t.getScope(); final String functionPrivateName =

Closure, 120

<FILEB>
<CHANGES>
if (ref.getSymbol().getScope()!= ref.scope) {
return false;
}
<CHANGEE>
<FILEE>
<FILEB> return references.get(i); } } return null; } /** * @return Whether the variable is only assigned a value once for its * lifetime. */ boolean isAssignedOnceInLifetime() { Reference ref = getOneAndOnlyAssignment(); if (ref == null) { return false; } // Make sure this assignment is not in a loop. for (BasicBlock block = ref.getBasicBlock(); block != null; block = block.getParent()) { if (block.isFunction) { <CHANGES> <CHANGEE> break; } else if (block.isLoop) { return false; } } return true; } /** * @return The one and only assignment. Returns if there are 0 or 2+ * assignments. */ private Reference getOneAndOnlyAssignment() { <FILEE> <SCANS> n.getFirstChild().getString(); if (functionPrivateName != null && functionPrivateName.length() > 0 && outerScope.isDeclared(functionPrivateName, false) && // Ideally, we would want to check whether the type in the scope // differs from the type being defined, but then the extern // redeclarations of built-in types generates spurious warnings. !(outerScope.getVar( functionPrivateName).getType() instanceof FunctionType)) { report(t, n, FUNCTION_MASKS_VARIABLE, functionPrivateName); } // TODO(user): Only traverse the function's body. The function's // name and arguments are traversed by the scope creator, and ideally // should not be traversed by the type checker. break; } return true; } /** * This is the meat of the type checking. It is basically one big switch, * with each case representing one type of parse tree node. The individual * cases are usually pretty straightforward. * * @param t The node traversal object that supplies context, such as the * scope chain to use in name lookups as well as error reporting. * @param n The node being visited. * @param parent The parent of the node n. */ @Override public void visit(NodeTraversal t, Node n, Node parent) { JSType childType; JSType leftType, rightType; Node left, right; // To be explicitly set to false if the node is not typeable. boolean typeable = true; switch (n.getType()) { case Token.CAST: Node expr = n.getFirstChild(); JSType exprType = getJSType(expr); JSType castType = getJSType(n); // TODO(johnlenz): determine if we can limit object literals in some // way. if (!expr.isObjectLit()) { validator.expectCanCast(t, n, castType, exprType); } ensureTyped(t, n, castType); if (castType.isSubtype(exprType) || expr.isObjectLit()) { expr.setJSType(castType); } break; case Token.NAME: typeable = visitName(t, n, parent); break; case Token.PARAM_LIST: typeable = false; break; case Token.COMMA: ensureTyped(t, n, getJSType(n.getLastChild())); break; case Token.TRUE: case Token.FALSE: ensureTyped(t, n, BOOLEAN_TYPE); break; case Token.THIS: ensureTyped(t, n, t.getScope().getTypeOfThis()); break; case Token.NULL: ensureTyped(t, n, NULL_TYPE); break; case Token.NUMBER: ensureTyped(t, n, NUMBER_TYPE); break; case Token.STRING: ensureTyped(t, n, STRING_TYPE); break; case Token.STRING_KEY: typeable = false; break; case Token.GETTER_DEF: case Token.SETTER_DEF: // Object literal keys are handled with OBJECTLIT break; case Token.ARRAYLIT: ensureTyped(t, n, ARRAY_TYPE); break; case Token.REGEXP: ensureTyped(t, n, REGEXP_TYPE); break; case Token.GETPROP: visitGetProp(t, n, parent); typeable = !(parent.isAssign() && parent.getFirstChild() == n); break; case Token.GETELEM: visitGetElem(t, n); // The type of GETELEM is always unknown

Closure, 120

<FILEB>
<CHANGES>
if (ref.getSymbol().getScope()!= ref.scope) {
return false;
}
<CHANGEE>
<FILEE>
<FILEB> return references.get(i); } } return null; } /** * @return Whether the variable is only assigned a value once for its * lifetime. */ boolean isAssignedOnceInLifetime() { Reference ref = getOneAndOnlyAssignment(); if (ref == null) { return false; } // Make sure this assignment is not in a loop. for (BasicBlock block = ref.getBasicBlock(); block != null; block = block.getParent()) { if (block.isFunction) { <CHANGES> <CHANGEE> break; } else if (block.isLoop) { return false; } } return true; } /** * @return The one and only assignment. Returns if there are 0 or 2+ * assignments. */ private Reference getOneAndOnlyAssignment() { <FILEE> <SCANS> */ obj.foo = true; JSType leftType = getJSType(lvalue); if (lvalue.isQualifiedName()) { // variable with inferred type case Var var = t.getScope().getVar(lvalue.getQualifiedName()); if (var != null) { if (var.isTypeInferred()) { return; } if (NodeUtil.getRootOfQualifiedName(lvalue).isThis() && t.getScope() != var.getScope()) { // Don't look at "this.foo" variables from other scopes. return; } if (var.getType() != null) { leftType = var.getType(); } } } // Fall through case for arbitrary LHS and arbitrary RHS. Node rightChild = assign.getLastChild(); JSType rightType = getJSType(rightChild); if (validator.expectCanAssignTo( t, assign, rightType, leftType, "assignment")) { ensureTyped(t, assign, rightType); } else { ensureTyped(t, assign); } } /** * After a struct object is created, we can't add new properties to it, with * one exception. We allow creation of "static" properties like * Foo.prototype.bar = baz; * where Foo.prototype is a struct, if the assignment happens at the top level * and the constructor Foo is defined in the same file. */ private void checkPropCreation(NodeTraversal t, Node lvalue) { if (lvalue.isGetProp()) { Node obj = lvalue.getFirstChild(); Node prop = lvalue.getLastChild(); JSType objType = getJSType(obj); String pname = prop.getString(); if (!objType.isStruct() || objType.hasProperty(pname)) { return; } Scope s = t.getScope(); if (obj.isThis() && getJSType(s.getRootNode()).isConstructor()) { return; } // Prop created outside ctor, check that it's a static prop Node assgnExp = lvalue.getParent(); Node assgnStm = assgnExp.getParent(); if (objType instanceof ObjectType && s.isGlobal() && NodeUtil.isPrototypePropertyDeclaration(assgnStm)) { ObjectType instance = objType.toObjectType().getOwnerFunction().getInstanceType(); String file = lvalue.getSourceFileName(); Node ctor = instance.getConstructor().getSource(); if (ctor != null && ctor.getSourceFileName().equals(file)) { JSType rvalueType = assgnExp.getLastChild().getJSType(); instance.defineInferredProperty(pname, rvalueType, lvalue); return; } } report(t, prop, ILLEGAL_PROPERTY_CREATION); } } private void checkPropertyInheritanceOnGetpropAssign( NodeTraversal t, Node assign, Node object, String property, JSDocInfo info, JSType propertyType) { // Inheritance checks for prototype properties. // // TODO(nicksantos): This isn't the right place to do this check. We // really want to do this when we're looking at the constructor. // We'd find all its properties and make sure they followed inheritance // rules, like we currently do for @implements to make sure // all the methods are implemented. // // As-is, this misses many other ways to override a property. // // object.prototype.property = ...; if (object.isGetProp()) { Node object2 = object.getFirstChild(); String property2 = NodeUtil.getStringValue(object.getLastChild()); if ("prototype".equals(property2))

Closure, 120

<FILEB>
<CHANGES>
if (ref.getSymbol().getScope()!= ref.scope) {
return false;
}
<CHANGEE>
<FILEE>
<FILEB> return references.get(i); } } return null; } /** * @return Whether the variable is only assigned a value once for its * lifetime. */ boolean isAssignedOnceInLifetime() { Reference ref = getOneAndOnlyAssignment(); if (ref == null) { return false; } // Make sure this assignment is not in a loop. for (BasicBlock block = ref.getBasicBlock(); block != null; block = block.getParent()) { if (block.isFunction) { <CHANGES> <CHANGEE> break; } else if (block.isLoop) { return false; } } return true; } /** * @return The one and only assignment. Returns if there are 0 or 2+ * assignments. */ private Reference getOneAndOnlyAssignment() { <FILEE> <SCANS> * @param parent The parent of the node n. * @return whether the node is typeable or not */ boolean visitName(NodeTraversal t, Node n, Node parent) { // At this stage, we need to determine whether this is a leaf // node in an expression (which therefore needs to have a type // assigned for it) versus some other decorative node that we // can safely ignore. Function names, arguments (children of LP nodes) and // variable declarations are ignored. // TODO(user): remove this short-circuiting in favor of a // pre order traversal of the FUNCTION, CATCH, LP and VAR nodes. int parentNodeType = parent.getType(); if (parentNodeType == Token.FUNCTION || parentNodeType == Token.CATCH || parentNodeType == Token.PARAM_LIST || parentNodeType == Token.VAR) { return false; } JSType type = n.getJSType(); if (type == null) { type = getNativeType(UNKNOWN_TYPE); Var var = t.getScope().getVar(n.getString()); if (var != null) { JSType varType = var.getType(); if (varType != null) { type = varType; } } } ensureTyped(t, n, type); return true; } /** * Visits a GETPROP node. * * @param t The node traversal object that supplies context, such as the * scope chain to use in name lookups as well as error reporting. * @param n The node being visited. * @param parent The parent of <code>n</code> */ private void visitGetProp(NodeTraversal t, Node n, Node parent) { // obj.prop or obj.method() // Lots of types can appear on the left, a call to a void function can // never be on the left. getPropertyType will decide what is acceptable // and what isn't. Node property = n.getLastChild(); Node objNode = n.getFirstChild(); JSType childType = getJSType(objNode); if (childType.isDict()) { report(t, property, TypeValidator.ILLEGAL_PROPERTY_ACCESS, "'.'", "dict"); } else if (validator.expectNotNullOrUndefined(t, n, childType, "No properties on this expression", getNativeType(OBJECT_TYPE))) { checkPropertyAccess(childType, property.getString(), t, n); } ensureTyped(t, n); } /** * Emit a warning if we can prove that a property cannot possibly be * defined on an object. Note the difference between JS and a strictly * statically typed language: we're checking if the property * *cannot be defined*, whereas a java compiler would check if the * property *can be undefined*. */ private void checkPropertyAccess(JSType childType, String propName, NodeTraversal t, Node n) { // If the property type is unknown, check the object type to see if it // can ever be defined. We explicitly exclude CHECKED_UNKNOWN (for // properties where we've checked that it exists, or for properties on // objects that aren't in this binary). JSType propType = getJSType(n); if (propType.isEquivalentTo(typeRegistry.getNativeType(UNKNOWN_TYPE))) { childType = childType.autobox(); ObjectType objectType = ObjectType.cast(childType); if (objectType != null) { // We special-case object types so that checks on enums can be // much stricter, and so that we can use hasProperty (which is much // faster in most cases). if (!object

Closure, 120

<FILEB>
<CHANGES>
if (ref.getSymbol().getScope()!= ref.scope) {
return false;
}
<CHANGEE>
<FILEE>
<FILEB> return references.get(i); } } return null; } /** * @return Whether the variable is only assigned a value once for its * lifetime. */ boolean isAssignedOnceInLifetime() { Reference ref = getOneAndOnlyAssignment(); if (ref == null) { return false; } // Make sure this assignment is not in a loop. for (BasicBlock block = ref.getBasicBlock(); block != null; block = block.getParent()) { if (block.isFunction) { <CHANGES> <CHANGEE> break; } else if (block.isLoop) { return false; } } return true; } /** * @return The one and only assignment. Returns if there are 0 or 2+ * assignments. */ private Reference getOneAndOnlyAssignment() { <FILEE> <SCANS> return NodeUtil.getConditionExpression(parent) == getProp; case Token.INSTANCEOF: case Token.TYPEOF: return true; case Token.AND: case Token.HOOK: return parent.getFirstChild() == getProp; case Token.NOT: return parent.getParent().isOr() && parent.getParent().getFirstChild() == parent; case Token.CAST: return isPropertyTest(parent); } return false; } /** * Visits a GETELEM node. * * @param t The node traversal object that supplies context, such as the * scope chain to use in name lookups as well as error reporting. * @param n The node being visited. */ private void visitGetElem(NodeTraversal t, Node n) { validator.expectIndexMatch( t, n, getJSType(n.getFirstChild()), getJSType(n.getLastChild())); ensureTyped(t, n); } /** * Visits a VAR node. * * @param t The node traversal object that supplies context, such as the * scope chain to use in name lookups as well as error reporting. * @param n The node being visited. */ private void visitVar(NodeTraversal t, Node n) { // TODO(nicksantos): Fix this so that the doc info always shows up // on the NAME node. We probably want to wait for the parser // merge to fix this. JSDocInfo varInfo = n.hasOneChild() ? n.getJSDocInfo() : null; for (Node name : n.children()) { Node value = name.getFirstChild(); // A null var would indicate a bug in the scope creation logic. Var var = t.getScope().getVar(name.getString()); if (value != null) { JSType valueType = getJSType(value); JSType nameType = var.getType(); nameType = (nameType == null) ? getNativeType(UNKNOWN_TYPE) : nameType; JSDocInfo info = name.getJSDocInfo(); if (info == null) { info = varInfo; } checkEnumAlias(t, info, value); if (var.isTypeInferred()) { ensureTyped(t, name, valueType); } else { validator.expectCanAssignTo( t, value, valueType, nameType, "initializing variable"); } } } } /** * Visits a NEW node. */ private void visitNew(NodeTraversal t, Node n) { Node constructor = n.getFirstChild(); JSType type = getJSType(constructor).restrictByNotNullOrUndefined(); if (type.isConstructor() || type.isEmptyType() || type.isUnknownType()) { FunctionType fnType = type.toMaybeFunctionType(); if (fnType != null && fnType.hasInstanceType()) { visitParameterList(t, n, fnType); ensureTyped(t, n, fnType.getInstanceType()); } else { ensureTyped(t, n); } } else { report(t, n, NOT_A_CONSTRUCTOR); ensureTyped(t, n); } } /** * Check whether there's any property conflict for for a particular super * interface * @param t The node traversal object that supplies context * @param n The node being visited * @param functionName The function name being checked * @param properties The property names in the super interfaces that have * been visited * @param currentProperties The property names in the super interface * that have been visited * @param interfaceType The super interface that

Closure, 120

<FILEB>
<CHANGES>
if (ref.getSymbol().getScope()!= ref.scope) {
return false;
}
<CHANGEE>
<FILEE>
<FILEB> return references.get(i); } } return null; } /** * @return Whether the variable is only assigned a value once for its * lifetime. */ boolean isAssignedOnceInLifetime() { Reference ref = getOneAndOnlyAssignment(); if (ref == null) { return false; } // Make sure this assignment is not in a loop. for (BasicBlock block = ref.getBasicBlock(); block != null; block = block.getParent()) { if (block.isFunction) { <CHANGES> <CHANGEE> break; } else if (block.isLoop) { return false; } } return true; } /** * @return The one and only assignment. Returns if there are 0 or 2+ * assignments. */ private Reference getOneAndOnlyAssignment() { <FILEE> <SCANS>MOD: case Token.ASSIGN_MUL: case Token.ASSIGN_SUB: case Token.DIV: case Token.MOD: case Token.MUL: case Token.SUB: validator.expectNumber(t, left, leftType, "left operand"); validator.expectNumber(t, right, rightType, "right operand"); break; case Token.ASSIGN_BITAND: case Token.ASSIGN_BITXOR: case Token.ASSIGN_BITOR: case Token.BITAND: case Token.BITXOR: case Token.BITOR: validator.expectBitwiseable(t, left, leftType, "bad left operand to bitwise operator"); validator.expectBitwiseable(t, right, rightType, "bad right operand to bitwise operator"); break; case Token.ASSIGN_ADD: case Token.ADD: break; default: report(t, n, UNEXPECTED_TOKEN, Token.name(op)); } ensureTyped(t, n); } /** * <p>Checks enum aliases. * * <p>We verify that the enum element type of the enum used * for initialization is a subtype of the enum element type of * the enum the value is being copied in.</p> * * <p>Example:</p> * <pre>var myEnum = myOtherEnum;</pre> * * <p>Enum aliases are irregular, so we need special code for this :(</p> * * @param value the value used for initialization of the enum */ private void checkEnumAlias( NodeTraversal t, JSDocInfo declInfo, Node value) { if (declInfo == null || !declInfo.hasEnumParameterType()) { return; } JSType valueType = getJSType(value); if (!valueType.isEnumType()) { return; } EnumType valueEnumType = valueType.toMaybeEnumType(); JSType valueEnumPrimitiveType = valueEnumType.getElementsType().getPrimitiveType(); validator.expectCanAssignTo(t, value, valueEnumPrimitiveType, declInfo.getEnumParameterType().evaluate(t.getScope(), typeRegistry), "incompatible enum element types"); } /** * This method gets the JSType from the Node argument and verifies that it is * present. */ private JSType getJSType(Node n) { JSType jsType = n.getJSType(); if (jsType == null) { // TODO(nicksantos): This branch indicates a compiler bug, not worthy of // halting the compilation but we should log this and analyze to track // down why it happens. This is not critical and will be resolved over // time as the type checker is extended. return getNativeType(UNKNOWN_TYPE); } else { return jsType; } } // TODO(nicksantos): TypeCheck should never be attaching types to nodes. // All types should be attached by TypeInference. This is not true today // for legacy reasons. There are a number of places where TypeInference // doesn't attach a type, as a signal to TypeCheck that it needs to check // that node's type. /** * Ensure that the given node has a type. If it does not have one, * attach the UNKNOWN_TYPE. */ private void ensureTyped(NodeTraversal t, Node n) { ensureTyped(t, n, getNativeType(UNKNOWN_TYPE)); } private void ensureTyped(NodeTraversal t, Node n, JSTypeNative type) { ensureTyped(t, n, getNativeType(type)); } /** * Enforces type cast

Closure, 120

<FILEB>
<CHANGES>
if (ref.getSymbol().getScope()!= ref.scope) {
return false;
}
<CHANGEE>
<FILEE>
<FILEB> return references.get(i); } } return null; } /** * @return Whether the variable is only assigned a value once for its * lifetime. */ boolean isAssignedOnceInLifetime() { Reference ref = getOneAndOnlyAssignment(); if (ref == null) { return false; } // Make sure this assignment is not in a loop. for (BasicBlock block = ref.getBasicBlock(); block != null; block = block.getParent()) { if (block.isFunction) { <CHANGES> <CHANGEE> break; } else if (block.isLoop) { return false; } } return true; } /** * @return The one and only assignment. Returns if there are 0 or 2+ * assignments. */ private Reference getOneAndOnlyAssignment() { <FILEE> <SCANS>/* * Copyright 2010 The Closure Compiler Authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.google.javascript.jscomp; import com.google.common.collect.ImmutableSet; import com.google.javascript.jscomp.NodeTraversal.AbstractPostOrderCallback; import com.google.javascript.jscomp.regex.RegExpTree; import com.google.javascript.rhino.Node; import com.google.javascript.rhino.Token; import java.util.Set; /** * Look for references to the global RegExp object that would cause * regular expressions to be unoptimizable, and checks that regular expressions * are syntactically valid. * * @author johnlenz@google.com (John Lenz) */ class CheckRegExp extends AbstractPostOrderCallback implements CompilerPass { static final DiagnosticType REGEXP_REFERENCE = DiagnosticType.warning("JSC_REGEXP_REFERENCE", "References to the global RegExp object prevents " + "optimization of regular expressions."); static final DiagnosticType MALFORMED_REGEXP = DiagnosticType.warning( "JSC_MALFORMED_REGEXP", "Malformed Regular Expression: {0}"); private static final Set<String> REGEXP_PROPERTY_BLACKLIST = ImmutableSet.of( "$1", "$2", "$3", "$4", "$5", "$6", "$7", "$8", "$9", "$_", "$input", // The following would also be blacklisted, but they aren't valid // identifiers, so can't be accessed with the '.' operator anyway. // "$*", "$&", "$+", "$`", "$'", "input", "lastMatch", "lastParen", "leftContext", "rightContext", "global", "ignoreCase", "lastIndex", "multiline", "source"); private final AbstractCompiler compiler; private boolean globalRegExpPropertiesUsed = false; public boolean isGlobalRegExpPropertiesUsed() { return globalRegExpPropertiesUsed; } public CheckRegExp(AbstractCompiler compiler) { this.compiler = compiler; } @Override public void process(Node externs, Node root) { NodeTraversal.traverse(compiler, root, this); } @Override public void visit(NodeTraversal t, Node n, Node parent) { if (NodeUtil.isReferenceName(n)) { String name = n.getString(); if (name.equals("RegExp") && t.getScope().getVar(name) == null) { int parentType = parent.getType(); boolean first = (n == parent.getFirstChild()); if (!((parentType == Token.NEW && first) || (parentType == Token.CALL && first) || (parentType == Token.INSTANCEOF && !first) || parentType == Token.EQ || parentType == Token.NE || parentType == Token.SHEQ || parentType == Token.SHNE || parentType == Token.CASE || (parentType == Token.GETPROP && first && !REGEXP_PROPERTY_BLACKLIST.contains( parent.getLastChild().getString())))) { t.report(n, REGEXP_REFERENCE); global

Closure, 120

<FILEB>
<CHANGES>
if (ref.getSymbol().getScope()!= ref.scope) {
return false;
}
<CHANGEE>
<FILEE>
<FILEB> return references.get(i); } } return null; } /** * @return Whether the variable is only assigned a value once for its * lifetime. */ boolean isAssignedOnceInLifetime() { Reference ref = getOneAndOnlyAssignment(); if (ref == null) { return false; } // Make sure this assignment is not in a loop. for (BasicBlock block = ref.getBasicBlock(); block != null; block = block.getParent()) { if (block.isFunction) { <CHANGES> <CHANGEE> break; } else if (block.isLoop) { return false; } } return true; } /** * @return The one and only assignment. Returns if there are 0 or 2+ * assignments. */ private Reference getOneAndOnlyAssignment() { <FILEE> <SCANS>/* * * ***** BEGIN LICENSE BLOCK ***** * Version: MPL 1.1/GPL 2.0 * * The contents of this file are subject to the Mozilla Public License Version * 1.1 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * http://www.mozilla.org/MPL/ * * Software distributed under the License is distributed on an "AS IS" basis, * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License * for the specific language governing rights and limitations under the * License. * * The Original Code is Rhino code, released * May 6, 1999. * * The Initial Developer of the Original Code is * Netscape Communications Corporation. * Portions created by the Initial Developer are Copyright (C) 1997-1999 * the Initial Developer. All Rights Reserved. * * Contributor(s): * Bob Jervis * Google Inc. * * Alternatively, the contents of this file may be used under the terms of * the GNU General Public License Version 2 or later (the "GPL"), in which * case the provisions of the GPL are applicable instead of those above. If * you wish to allow use of your version of this file only under the terms of * the GPL and not to allow others to use your version of this file under the * MPL, indicate your decision by deleting the provisions above and replacing * them with the notice and other provisions required by the GPL. If you do * not delete the provisions above, a recipient may use your version of this * file under either the MPL or the GPL. * * ***** END LICENSE BLOCK ***** */ package com.google.javascript.rhino.jstype; import com.google.javascript.rhino.ErrorReporter; /** * Value types (null, void, number, boolean, string). */ abstract class ValueType extends JSType { ValueType(JSTypeRegistry registry) { super(registry); } @Override final JSType resolveInternal(ErrorReporter t, StaticScope<JSType> scope) { return this; } @Override public boolean hasDisplayName() { return true; } @Override <T> T visit(RelationshipVisitor<T> visitor, JSType that) { return visitor.caseValueType(this, that); } }

Closure, 120

<FILEB>
<CHANGES>
if (ref.getSymbol().getScope()!= ref.scope) {
return false;
}
<CHANGEE>
<FILEE>
<FILEB> return references.get(i); } } return null; } /** * @return Whether the variable is only assigned a value once for its * lifetime. */ boolean isAssignedOnceInLifetime() { Reference ref = getOneAndOnlyAssignment(); if (ref == null) { return false; } // Make sure this assignment is not in a loop. for (BasicBlock block = ref.getBasicBlock(); block != null; block = block.getParent()) { if (block.isFunction) { <CHANGES> <CHANGEE> break; } else if (block.isLoop) { return false; } } return true; } /** * @return The one and only assignment. Returns if there are 0 or 2+ * assignments. */ private Reference getOneAndOnlyAssignment() { <FILEE> <SCANS> after finishing a scope for (Iterator<Var> it = t.getScope().getVars(); it.hasNext();) { Var v = it.next(); checkVar(v, referenceMap.getReferences(v).references); } } /** * If the variable is declared more than once in a basic block, generate a * warning. Also check if a variable is used in a given scope before it is * declared, which suggest a likely error. Relies on the fact that * references is in parse-tree order. */ private void checkVar(Var v, List<Reference> references) { blocksWithDeclarations.clear(); boolean isDeclaredInScope = false; boolean isUnhoistedNamedFunction = false; Reference hoistedFn = null; // Look for hoisted functions. for (Reference reference : references) { if (reference.isHoistedFunction()) { blocksWithDeclarations.add(reference.getBasicBlock()); isDeclaredInScope = true; hoistedFn = reference; break; } else if (NodeUtil.isFunctionDeclaration( reference.getNode().getParent())) { isUnhoistedNamedFunction = true; } } for (Reference reference : references) { if (reference == hoistedFn) { continue; } BasicBlock basicBlock = reference.getBasicBlock(); boolean isDeclaration = reference.isDeclaration(); boolean allowDupe = SyntacticScopeCreator.hasDuplicateDeclarationSuppression( reference.getNode(), v); if (isDeclaration && !allowDupe) { // Look through all the declarations we've found so far, and // check if any of them are before this block. for (BasicBlock declaredBlock : blocksWithDeclarations) { if (declaredBlock.provablyExecutesBefore(basicBlock)) { // TODO(johnlenz): Fix AST generating clients that so they would // have property StaticSourceFile attached at each node. Or // better yet, make sure the generated code never violates // the requirement to pass aggressive var check! String filename = NodeUtil.getSourceName(reference.getNode()); compiler.report( JSError.make(filename, reference.getNode(), checkLevel, REDECLARED_VARIABLE, v.name)); break; } } } if (isUnhoistedNamedFunction && !isDeclaration && isDeclaredInScope) { // Only allow an unhoisted named function to be used within the // block it is declared. for (BasicBlock declaredBlock : blocksWithDeclarations) { if (!declaredBlock.provablyExecutesBefore(basicBlock)) { String filename = NodeUtil.getSourceName(reference.getNode()); compiler.report( JSError.make(filename, reference.getNode(), AMBIGUOUS_FUNCTION_DECL, v.name)); break; } } } if (!isDeclaration && !isDeclaredInScope) { // Don't check the order of refer in externs files. if (!reference.getNode().isFromExterns()) { // Special case to deal with var goog = goog || {} Node grandparent = reference.getGrandparent(); if (grandparent.isName() && grandparent.getString() == v.name) { continue; } // Only generate warnings if the scopes do not match in order // to deal with possible forward declarations and recursion if (reference.getScope() == v.scope) { String filename = NodeUtil.getSourceName(reference.getNode()); compiler.report( JSError.make(filename, reference.getNode(), checkLevel, UNDECLARED_REFERENCE, v.name)); } } } if (isDeclaration) { blocksWithDeclarations.add

Closure, 120

<FILEB>
<CHANGES>
if (ref.getSymbol().getScope()!= ref.scope) {
return false;
}
<CHANGEE>
<FILEE>
<FILEB> return references.get(i); } } return null; } /** * @return Whether the variable is only assigned a value once for its * lifetime. */ boolean isAssignedOnceInLifetime() { Reference ref = getOneAndOnlyAssignment(); if (ref == null) { return false; } // Make sure this assignment is not in a loop. for (BasicBlock block = ref.getBasicBlock(); block != null; block = block.getParent()) { if (block.isFunction) { <CHANGES> <CHANGEE> break; } else if (block.isLoop) { return false; } } return true; } /** * @return The one and only assignment. Returns if there are 0 or 2+ * assignments. */ private Reference getOneAndOnlyAssignment() { <FILEE> <SCANS>, that.toMaybeRecordType()); } // Interfaces // Find all the interfaces implemented by this class and compare each one // to the interface instance. ObjectType thatObj = that.toObjectType(); FunctionType thatCtor = thatObj == null ? null : thatObj.getConstructor(); if (getConstructor() != null && getConstructor().isInterface()) { for (ObjectType thisInterface : getCtorExtendedInterfaces()) { if (thisInterface.isSubtype(that)) { return true; } } } else if (thatCtor != null && thatCtor.isInterface()) { Iterable<ObjectType> thisInterfaces = getCtorImplementedInterfaces(); for (ObjectType thisInterface : thisInterfaces) { if (thisInterface.isSubtype(that)) { return true; } } } // other prototype based objects if (isUnknownType() || implicitPrototypeChainIsUnknown()) { // If unsure, say 'yes', to avoid spurious warnings. // TODO(user): resolve the prototype chain completely in all cases, // to avoid guessing. return true; } return thatObj != null && isImplicitPrototype(thatObj); } private boolean implicitPrototypeChainIsUnknown() { ObjectType p = getImplicitPrototype(); while (p != null) { if (p.isUnknownType()) { return true; } p = p.getImplicitPrototype(); } return false; } @Override public boolean hasCachedValues() { return super.hasCachedValues(); } /** Whether this is a built-in object. */ @Override public boolean isNativeObjectType() { return nativeType; } @Override void setOwnerFunction(FunctionType type) { Preconditions.checkState(ownerFunction == null || type == null); ownerFunction = type; } @Override public FunctionType getOwnerFunction() { return ownerFunction; } @Override public Iterable<ObjectType> getCtorImplementedInterfaces() { return isFunctionPrototypeType() ? getOwnerFunction().getImplementedInterfaces() : ImmutableList.<ObjectType>of(); } @Override public Iterable<ObjectType> getCtorExtendedInterfaces() { return isFunctionPrototypeType() ? getOwnerFunction().getExtendedInterfaces() : ImmutableList.<ObjectType>of(); } @Override JSType resolveInternal(ErrorReporter t, StaticScope<JSType> scope) { setResolvedTypeInternal(this); ObjectType implicitPrototype = getImplicitPrototype(); if (implicitPrototype != null) { implicitPrototypeFallback = (ObjectType) implicitPrototype.resolve(t, scope); } for (Property prop : properties.values()) { prop.setType(safeResolve(prop.getType(), t, scope)); } return this; } @Override public void matchConstraint(JSType constraint) { // We only want to match constraints on anonymous types. if (hasReferenceName()) { return; } // Handle the case where the constraint object is a record type. // // param constraint {{prop: (number|undefined)}} // function f(constraint) {} // f({}); // // We want to modify the object literal to match the constraint, by // taking any each property on the record and trying to match // properties on this object. if (constraint.isRecordType()) { matchRecordTypeConstraint(constraint.toObjectType()); } else if (constraint.isUnionType()) { for (JSType alt : constraint.toMaybeUnionType().getAlternates()) { if (alt.isRecordType()) { matchRecordTypeConstraint(alt.toObjectType()); } } } } public void matchRecordTypeConstraint(ObjectType constraintObj) { for (String prop : constraintObj.getOwnProperty

Closure, 120

<FILEB>
<CHANGES>
if (ref.getSymbol().getScope()!= ref.scope) {
return false;
}
<CHANGEE>
<FILEE>
<FILEB> return references.get(i); } } return null; } /** * @return Whether the variable is only assigned a value once for its * lifetime. */ boolean isAssignedOnceInLifetime() { Reference ref = getOneAndOnlyAssignment(); if (ref == null) { return false; } // Make sure this assignment is not in a loop. for (BasicBlock block = ref.getBasicBlock(); block != null; block = block.getParent()) { if (block.isFunction) { <CHANGES> <CHANGEE> break; } else if (block.isLoop) { return false; } } return true; } /** * @return The one and only assignment. Returns if there are 0 or 2+ * assignments. */ private Reference getOneAndOnlyAssignment() { <FILEE> <SCANS>/* * Copyright 2006 The Closure Compiler Authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.google.javascript.jscomp; import com.google.common.base.Preconditions; import com.google.javascript.rhino.InputId; import com.google.javascript.rhino.JSDocInfo; import com.google.javascript.rhino.Node; import com.google.javascript.rhino.Token; /** * <p>The syntactic scope creator scans the parse tree to create a Scope object * containing all the variable declarations in that scope.</p> * * <p>This implementation is not thread-safe.</p> * */ class SyntacticScopeCreator implements ScopeCreator { private final AbstractCompiler compiler; private Scope scope; private InputId inputId; private final RedeclarationHandler redeclarationHandler; // The arguments variable is special, in that it's declared in every local // scope, but not explicitly declared. private static final String ARGUMENTS = "arguments"; public static final DiagnosticType VAR_MULTIPLY_DECLARED_ERROR = DiagnosticType.error( "JSC_VAR_MULTIPLY_DECLARED_ERROR", "Variable {0} first declared in {1}"); public static final DiagnosticType VAR_ARGUMENTS_SHADOWED_ERROR = DiagnosticType.error( "JSC_VAR_ARGUMENTS_SHADOWED_ERROR", "Shadowing \"arguments\" is not allowed"); /** * Creates a ScopeCreator. */ SyntacticScopeCreator(AbstractCompiler compiler) { this.compiler = compiler; this.redeclarationHandler = new DefaultRedeclarationHandler(); } SyntacticScopeCreator( AbstractCompiler compiler, RedeclarationHandler redeclarationHandler) { this.compiler = compiler; this.redeclarationHandler = redeclarationHandler; } @Override public Scope createScope(Node n, Scope parent) { inputId = null; if (parent == null) { scope = Scope.createGlobalScope(n); } else { scope = new Scope(parent, n); } scanRoot(n); inputId = null; Scope returnedScope = scope; scope = null; return returnedScope; } private void scanRoot(Node n) { if (n.isFunction()) { if (inputId == null) { inputId = NodeUtil.getInputId(n); // TODO(johnlenz): inputId maybe null if the FUNCTION node is detached // from the AST. // Is it meaningful to build a scope for detached FUNCTION node? } final Node fnNameNode = n.getFirstChild(); final Node args = fnNameNode.getNext(); final Node body = args.getNext(); // Bleed the function name into the scope, if it hasn't // been declared in the outer scope. String fnName = fnNameNode.getString(); if (!fnName.isEmpty() && NodeUtil.isFunctionExpression(n)) { declareVar(fnNameNode); } // Args: Declare function variables Preconditions.checkState(args.isParamList()); for (Node

Closure, 120

<FILEB>
<CHANGES>
if (ref.getSymbol().getScope()!= ref.scope) {
return false;
}
<CHANGEE>
<FILEE>
<FILEB> return references.get(i); } } return null; } /** * @return Whether the variable is only assigned a value once for its * lifetime. */ boolean isAssignedOnceInLifetime() { Reference ref = getOneAndOnlyAssignment(); if (ref == null) { return false; } // Make sure this assignment is not in a loop. for (BasicBlock block = ref.getBasicBlock(); block != null; block = block.getParent()) { if (block.isFunction) { <CHANGES> <CHANGEE> break; } else if (block.isLoop) { return false; } } return true; } /** * @return The one and only assignment. Returns if there are 0 or 2+ * assignments. */ private Reference getOneAndOnlyAssignment() { <FILEE> <SCANS> a = args.getFirstChild(); a != null; a = a.getNext()) { Preconditions.checkState(a.isName()); declareVar(a); } // Body scanVars(body); } else { // It's the global block Preconditions.checkState(scope.getParent() == null); scanVars(n); } } /** * Scans and gather variables declarations under a Node */ private void scanVars(Node n) { switch (n.getType()) { case Token.VAR: // Declare all variables. e.g. var x = 1, y, z; for (Node child = n.getFirstChild(); child != null;) { Node next = child.getNext(); declareVar(child); child = next; } return; case Token.FUNCTION: if (NodeUtil.isFunctionExpression(n)) { return; } String fnName = n.getFirstChild().getString(); if (fnName.isEmpty()) { // This is invalid, but allow it so the checks can catch it. return; } declareVar(n.getFirstChild()); return; // should not examine function's children case Token.CATCH: Preconditions.checkState(n.getChildCount() == 2); Preconditions.checkState(n.getFirstChild().isName()); // the first child is the catch var and the third child // is the code block final Node var = n.getFirstChild(); final Node block = var.getNext(); declareVar(var); scanVars(block); return; // only one child to scan case Token.SCRIPT: inputId = n.getInputId(); Preconditions.checkNotNull(inputId); break; } // Variables can only occur in statement-level nodes, so // we only need to traverse children in a couple special cases. if (NodeUtil.isControlStructure(n) || NodeUtil.isStatementBlock(n)) { for (Node child = n.getFirstChild(); child != null;) { Node next = child.getNext(); scanVars(child); child = next; } } } /** * Interface for injectable duplicate handling. */ interface RedeclarationHandler { void onRedeclaration( Scope s, String name, Node n, CompilerInput input); } /** * The default handler for duplicate declarations. */ private class DefaultRedeclarationHandler implements RedeclarationHandler { @Override public void onRedeclaration( Scope s, String name, Node n, CompilerInput input) { Node parent = n.getParent(); // Don't allow multiple variables to be declared at the top-level scope if (scope.isGlobal()) { Scope.Var origVar = scope.getVar(name); Node origParent = origVar.getParentNode(); if (origParent.isCatch() && parent.isCatch()) { // Okay, both are 'catch(x)' variables. return; } boolean allowDupe = hasDuplicateDeclarationSuppression(n, origVar); if (!allowDupe) { compiler.report( JSError.make(NodeUtil.getSourceName(n), n, VAR_MULTIPLY_DECLARED_ERROR, name, (origVar.input != null ? origVar.input.getName() : "??"))); } } else if (name.equals(ARGUMENTS) && !NodeUtil.isVarDeclaration(n)) { // Disallow shadowing "arguments" as we can't handle with our current // scope modeling. compiler.report( JSError.make(NodeUtil.getSourceName(n), n, VAR_ARGUMENTS_SHADOWED_ERROR));

Closure, 120

<FILEB>
<CHANGES>
if (ref.getSymbol().getScope()!= ref.scope) {
return false;
}
<CHANGEE>
<FILEE>
<FILEB> return references.get(i); } } return null; } /** * @return Whether the variable is only assigned a value once for its * lifetime. */ boolean isAssignedOnceInLifetime() { Reference ref = getOneAndOnlyAssignment(); if (ref == null) { return false; } // Make sure this assignment is not in a loop. for (BasicBlock block = ref.getBasicBlock(); block != null; block = block.getParent()) { if (block.isFunction) { <CHANGES> <CHANGEE> break; } else if (block.isLoop) { return false; } } return true; } /** * @return The one and only assignment. Returns if there are 0 or 2+ * assignments. */ private Reference getOneAndOnlyAssignment() { <FILEE> <SCANS> } } } /** * Declares a variable. * * @param n The node corresponding to the variable name. */ private void declareVar(Node n) { Preconditions.checkState(n.isName()); CompilerInput input = compiler.getInput(inputId); String name = n.getString(); if (scope.isDeclared(name, false) || (scope.isLocal() && name.equals(ARGUMENTS))) { redeclarationHandler.onRedeclaration( scope, name, n, input); } else { scope.declare(name, n, null, input); } } /** * @param n The name node to check. * @param origVar The associated Var. * @return Whether duplicated declarations warnings should be suppressed * for the given node. */ static boolean hasDuplicateDeclarationSuppression(Node n, Scope.Var origVar) { Preconditions.checkState(n.isName()); Node parent = n.getParent(); Node origParent = origVar.getParentNode(); JSDocInfo info = n.getJSDocInfo(); if (info == null) { info = parent.getJSDocInfo(); } if (info != null && info.getSuppressions().contains("duplicate")) { return true; } info = origVar.nameNode.getJSDocInfo(); if (info == null) { info = origParent.getJSDocInfo(); } return (info != null && info.getSuppressions().contains("duplicate")); } /** * Generates an untyped global scope from the root of AST of compiler (which * includes externs). * * @param compiler The compiler for which the scope is generated. * @return The new untyped global scope generated as a result of this call. */ static Scope generateUntypedTopScope(AbstractCompiler compiler) { return new SyntacticScopeCreator(compiler).createScope(compiler.getRoot(), null); } }

Closure, 120

<FILEB>
<CHANGES>
if (ref.getSymbol().getScope()!= ref.scope) {
return false;
}
<CHANGEE>
<FILEE>
<FILEB> return references.get(i); } } return null; } /** * @return Whether the variable is only assigned a value once for its * lifetime. */ boolean isAssignedOnceInLifetime() { Reference ref = getOneAndOnlyAssignment(); if (ref == null) { return false; } // Make sure this assignment is not in a loop. for (BasicBlock block = ref.getBasicBlock(); block != null; block = block.getParent()) { if (block.isFunction) { <CHANGES> <CHANGEE> break; } else if (block.isLoop) { return false; } } return true; } /** * @return The one and only assignment. Returns if there are 0 or 2+ * assignments. */ private Reference getOneAndOnlyAssignment() { <FILEE> <SCANS> StrictModeCheck( AbstractCompiler compiler, boolean noVarCheck, boolean noCajaChecks) { this.compiler = compiler; this.noVarCheck = noVarCheck; this.noCajaChecks = noCajaChecks; } @Override public void process(Node externs, Node root) { NodeTraversal.traverseRoots( compiler, Lists.newArrayList(externs, root), this); NodeTraversal.traverse(compiler, root, new NonExternChecks()); } @Override public void visit(NodeTraversal t, Node n, Node parent) { if (n.isFunction()) { checkFunctionUse(t, n); } else if (n.isName()) { if (!isDeclaration(n)) { checkNameUse(t, n); } } else if (n.isAssign()) { checkAssignment(t, n); } else if (n.isDelProp()) { checkDelete(t, n); } else if (n.isObjectLit()) { checkObjectLiteral(t, n); } else if (n.isLabel()) { checkLabel(t, n); } } /** Checks that the function is used legally. */ private void checkFunctionUse(NodeTraversal t, Node n) { if (NodeUtil.isFunctionDeclaration(n) && !NodeUtil.isHoistedFunctionDeclaration(n)) { t.report(n, BAD_FUNCTION_DECLARATION); } } /** * Determines if the given name is a declaration, which can be a declaration * of a variable, function, or argument. */ private static boolean isDeclaration(Node n) { switch (n.getParent().getType()) { case Token.VAR: case Token.FUNCTION: case Token.CATCH: return true; case Token.PARAM_LIST: return n.getParent().getParent().isFunction(); default: return false; } } /** Checks that the given name is used legally. */ private void checkNameUse(NodeTraversal t, Node n) { Var v = t.getScope().getVar(n.getString()); if (v == null) { // In particular, this prevents creating a global variable by assigning // to it without a declaration. if (!noVarCheck) { t.report(n, UNKNOWN_VARIABLE, n.getString()); } } if (!noCajaChecks) { if ("eval".equals(n.getString())) { t.report(n, EVAL_USE); } else if (n.getString().endsWith("__")) { t.report(n, ILLEGAL_NAME); } } } /** Checks that an assignment is not to the "arguments" object. */ private void checkAssignment(NodeTraversal t, Node n) { if (n.getFirstChild().isName()) { if ("arguments".equals(n.getFirstChild().getString())) { t.report(n, ARGUMENTS_ASSIGNMENT); } else if ("eval".equals(n.getFirstChild().getString())) { // Note that assignment to eval is already illegal because any use of // that name is illegal. if (noCajaChecks) { t.report(n, EVAL_ASSIGNMENT); } } } } /** Checks that variables, functions, and arguments are not deleted. */ private void checkDelete(NodeTraversal t, Node n) { if (n.getFirstChild().isName()) { Var v = t.getScope().getVar(n.getFirstChild().getString()); if (v != null) { t.report(n, DELETE_VARIABLE); } } } /** Checks that object literal keys are valid. */

Closure, 120

<FILEB>
<CHANGES>
if (ref.getSymbol().getScope()!= ref.scope) {
return false;
}
<CHANGEE>
<FILEE>
<FILEB> return references.get(i); } } return null; } /** * @return Whether the variable is only assigned a value once for its * lifetime. */ boolean isAssignedOnceInLifetime() { Reference ref = getOneAndOnlyAssignment(); if (ref == null) { return false; } // Make sure this assignment is not in a loop. for (BasicBlock block = ref.getBasicBlock(); block != null; block = block.getParent()) { if (block.isFunction) { <CHANGES> <CHANGEE> break; } else if (block.isLoop) { return false; } } return true; } /** * @return The one and only assignment. Returns if there are 0 or 2+ * assignments. */ private Reference getOneAndOnlyAssignment() { <FILEE> <SCANS> worth the effort to fix, since they've // already been translated anyway. String desc = extractedMessage.getDesc(); if (isNewStyleMessage && (desc == null || desc.trim().isEmpty()) && !extractedMessage.isExternal()) { compiler.report(traversal.makeError(node, MESSAGE_HAS_NO_DESCRIPTION, messageKey)); } JsMessageDefinition msgDefinition = new JsMessageDefinition( node, msgNode, msgNodeParent); processJsMessage(extractedMessage, msgDefinition); } /** * Track a message for later retrieval. * * This is used for tracking duplicates, and for figuring out message * fallback. Not all message types are trackable, because that would * require a more sophisticated analysis. e.g., * function f(s) { s.MSG_UNNAMED_X = 'Some untrackable message'; } */ private void trackMessage( NodeTraversal t, JsMessage message, String msgName, Node msgNode, boolean isUnnamedMessage) { if (!isUnnamedMessage) { MessageLocation location = new MessageLocation(message, msgNode); messageNames.put(msgName, location); } else if (msgNode.isName()) { Var var = t.getScope().getVar(msgName); if (var != null) { unnamedMessages.put(var, message); } } } /** Get a previously tracked message. */ private JsMessage getTrackedMessage(NodeTraversal t, String msgName) { boolean isUnnamedMessage = isUnnamedMessageName(msgName); if (!isUnnamedMessage) { MessageLocation location = messageNames.get(msgName); return location == null ? null : location.message; } else { Var var = t.getScope().getVar(msgName); if (var != null) { return unnamedMessages.get(var); } } return null; } /** * Checks if message already processed. If so - it generates 'message * duplicated' compiler error. * * @param msgName the name of the message * @param msgNode the node that represents JS message */ private void checkIfMessageDuplicated(String msgName, Node msgNode) { if (messageNames.containsKey(msgName)) { MessageLocation location = messageNames.get(msgName); compiler.report(JSError.make(msgNode, MESSAGE_DUPLICATE_KEY, msgName, location.messageNode.getSourceFileName(), Integer.toString(location.messageNode.getLineno()))); } } /** * Creates a {@link JsMessage} for a JS message defined using a JS variable * declaration (e.g <code>var MSG_X = ...;</code>). * * @param builder the message builder * @param nameNode a NAME node for a JS message variable * @param parentNode a VAR node, parent of {@code nameNode} * @param grandParentNode the grandparent of {@code nameNode}. This node is * only used to get meta data about the message that might be * surrounding it (e.g. a message description). This argument may be * null if the meta data is not needed. * @throws MalformedException if {@code varNode} does not * correspond to a valid JS message VAR node */ private void extractMessageFromVariable( Builder builder, Node nameNode, Node parentNode, @Nullable Node grandParentNode) throws MalformedException { // Determine the message's value Node valueNode = nameNode.getFirstChild(); switch (valueNode.getType()) { case Token.STRING: case Token.ADD: maybeInitMetaData

Closure, 120

<FILEB>
<CHANGES>
if (ref.getSymbol().getScope()!= ref.scope) {
return false;
}
<CHANGEE>
<FILEE>
<FILEB> return references.get(i); } } return null; } /** * @return Whether the variable is only assigned a value once for its * lifetime. */ boolean isAssignedOnceInLifetime() { Reference ref = getOneAndOnlyAssignment(); if (ref == null) { return false; } // Make sure this assignment is not in a loop. for (BasicBlock block = ref.getBasicBlock(); block != null; block = block.getParent()) { if (block.isFunction) { <CHANGES> <CHANGEE> break; } else if (block.isLoop) { return false; } } return true; } /** * @return The one and only assignment. Returns if there are 0 or 2+ * assignments. */ private Reference getOneAndOnlyAssignment() { <FILEE> <SCANS>.getRootScope()); } else { scopes.push(typeSystem.getFunctionScope(t.getScopeRoot())); } } @Override public void exitScope(NodeTraversal t) { scopes.pop(); } /** Returns the current scope at this point in the file. */ protected StaticScope<T> getScope() { return scopes.peek(); } } /** * Finds all properties defined in the externs file and sets them as * ineligible for renaming from the type on which they are defined. */ private class FindExternProperties extends AbstractScopingCallback { @Override public void visit(NodeTraversal t, Node n, Node parent) { // TODO(johnlenz): Support object-literal property definitions. if (n.isGetProp()) { String field = n.getLastChild().getString(); T type = typeSystem.getType(getScope(), n.getFirstChild(), field); Property prop = getProperty(field); if (typeSystem.isInvalidatingType(type)) { prop.invalidate(); } else { prop.addTypeToSkip(type); // If this is a prototype property, then we want to skip assignments // to the instance type as well. These assignments are not usually // seen in the extern code itself, so we must handle them here. if ((type = typeSystem.getInstanceFromPrototype(type)) != null) { prop.getTypes().add(type); prop.typesToSkip.add(type); } } } } } /** * Traverses the tree, building a map from field names to Nodes for all * fields that can be renamed. */ private class FindRenameableProperties extends AbstractScopingCallback { @Override public void visit(NodeTraversal t, Node n, Node parent) { if (n.isGetProp()) { handleGetProp(t, n); } else if (n.isObjectLit()) { handleObjectLit(t, n); } } /** * Processes a GETPROP node. */ private void handleGetProp(NodeTraversal t, Node n) { String name = n.getLastChild().getString(); T type = typeSystem.getType(getScope(), n.getFirstChild(), name); Property prop = getProperty(name); if (!prop.scheduleRenaming(n.getLastChild(), processProperty(t, prop, type, null))) { if (propertiesToErrorFor.containsKey(name)) { String suggestion = ""; if (type instanceof JSType) { JSType jsType = (JSType) type; if (jsType.isAllType() || jsType.isUnknownType()) { if (n.getFirstChild().isThis()) { suggestion = "The \"this\" object is unknown in the function," + "consider using @this"; } else { String qName = n.getFirstChild().getQualifiedName(); suggestion = "Consider casting " + qName + " if you know it's type."; } } else { List<String> errors = Lists.newArrayList(); printErrorLocations(errors, jsType); if (!errors.isEmpty()) { suggestion = "Consider fixing errors for the following types:\n"; suggestion += Joiner.on("\n").join(errors); } } } compiler.report(JSError.make( t.getSourceName(), n, propertiesToErrorFor.get(name), Warnings.INVALIDATION, name, (type == null ? "null" : type.toString()), n.toString(), suggestion)); } } } /** * Processes a OBJECTLIT node. */ private void handleObjectLit(NodeTraversal t

Closure, 120

<FILEB>
<CHANGES>
if (ref.getSymbol().getScope()!= ref.scope) {
return false;
}
<CHANGEE>
<FILEE>
<FILEB> return references.get(i); } } return null; } /** * @return Whether the variable is only assigned a value once for its * lifetime. */ boolean isAssignedOnceInLifetime() { Reference ref = getOneAndOnlyAssignment(); if (ref == null) { return false; } // Make sure this assignment is not in a loop. for (BasicBlock block = ref.getBasicBlock(); block != null; block = block.getParent()) { if (block.isFunction) { <CHANGES> <CHANGEE> break; } else if (block.isLoop) { return false; } } return true; } /** * @return The one and only assignment. Returns if there are 0 or 2+ * assignments. */ private Reference getOneAndOnlyAssignment() { <FILEE> <SCANS>, Node n) { Node child = n.getFirstChild(); while (child != null) { // Maybe STRING, GET, SET // We should never see a mix of numbers and strings. String name = child.getString(); T type = typeSystem.getType(getScope(), n, name); Property prop = getProperty(name); if (!prop.scheduleRenaming(child, processProperty(t, prop, type, null))) { // TODO(user): It doesn't look like the user can do much in this // case right now. if (propertiesToErrorFor.containsKey(name)) { compiler.report(JSError.make( t.getSourceName(), child, propertiesToErrorFor.get(name), Warnings.INVALIDATION, name, (type == null ? "null" : type.toString()), n.toString(), "")); } } child = child.getNext(); } } private void printErrorLocations(List<String> errors, JSType t) { if (!t.isObject() || t.isAllType()) { return; } if (t.isUnionType()) { for (JSType alt : t.toMaybeUnionType().getAlternates()) { printErrorLocations(errors, alt); } return; } for (JSError error : invalidationMap.get(t)) { if (errors.size() > MAX_INVALDIATION_WARNINGS_PER_PROPERTY) { return; } errors.add( t.toString() + " at " + error.sourceName + ":" + error.lineNumber); } } /** * Processes a property, adding it to the list of properties to rename. * @return a representative type for the property reference, which will be * the highest type on the prototype chain of the provided type. In the * case of a union type, it will be the highest type on the prototype * chain of one of the members of the union. */ private T processProperty( NodeTraversal t, Property prop, T type, T relatedType) { type = typeSystem.restrictByNotNullOrUndefined(type); if (prop.skipRenaming || typeSystem.isInvalidatingType(type)) { return null; } Iterable<T> alternatives = typeSystem.getTypeAlternatives(type); if (alternatives != null) { T firstType = relatedType; for (T subType : alternatives) { T lastType = processProperty(t, prop, subType, firstType); if (lastType != null) { firstType = firstType == null ? lastType : firstType; } } return firstType; } else { T topType = typeSystem.getTypeWithProperty(prop.name, type); if (typeSystem.isInvalidatingType(topType)) { return null; } prop.addType(type, topType, relatedType); return topType; } } } /** Renames all properties with references on more than one type. */ void renameProperties() { int propsRenamed = 0, propsSkipped = 0, instancesRenamed = 0, instancesSkipped = 0, singleTypeProps = 0; Set<String> reported = Sets.newHashSet(); for (Property prop : properties.values()) { if (prop.shouldRename()) { Map<T, String> propNames = buildPropNames(prop.getTypes(), prop.name); ++propsRenamed; prop.expandTypesToSkip(); for (Node node : prop.renameNodes) { T rootType = prop.rootTypes.get(node); if (prop

Closure, 120

<FILEB>
<CHANGES>
if (ref.getSymbol().getScope()!= ref.scope) {
return false;
}
<CHANGEE>
<FILEE>
<FILEB> return references.get(i); } } return null; } /** * @return Whether the variable is only assigned a value once for its * lifetime. */ boolean isAssignedOnceInLifetime() { Reference ref = getOneAndOnlyAssignment(); if (ref == null) { return false; } // Make sure this assignment is not in a loop. for (BasicBlock block = ref.getBasicBlock(); block != null; block = block.getParent()) { if (block.isFunction) { <CHANGES> <CHANGEE> break; } else if (block.isLoop) { return false; } } return true; } /** * @return The one and only assignment. Returns if there are 0 or 2+ * assignments. */ private Reference getOneAndOnlyAssignment() { <FILEE> <SCANS> add in the native * types since we don't track them, but only if they have the given * property. */ T getType(StaticScope<T> scope, Node node, String prop); /** * Returns true if a field reference on this type will invalidate all * references to that field as candidates for renaming. This is true if the * type is unknown or all-inclusive, as variables with such a type could be * references to any object. */ boolean isInvalidatingType(T type); /** * Informs the given type system that a type is invalidating due to a type * mismatch found during type checking. */ void addInvalidatingType(JSType type); /** * Returns a set of types that should be skipped given the given type. * This is necessary for interfaces when using JSTypes, as all super * interfaces must also be skipped. */ ImmutableSet<T> getTypesToSkipForType(T type); /** * Determines whether the given type is one whose properties should not be * considered for renaming. */ boolean isTypeToSkip(T type); /** Remove null and undefined from the options in the given type. */ T restrictByNotNullOrUndefined(T type); /** * Returns the alternatives if this is a type that represents multiple * types, and null if not. Union and interface types can correspond to * multiple other types. */ Iterable<T> getTypeAlternatives(T type); /** * Returns the type in the chain from the given type that contains the given * field or null if it is not found anywhere. */ T getTypeWithProperty(String field, T type); /** * Returns the type of the instance of which this is the prototype or null * if this is not a function prototype. */ T getInstanceFromPrototype(T type); /** * Records that this property could be referenced from any interface that * this type, or any type in its superclass chain, implements. */ void recordInterfaces(T type, T relatedType, DisambiguateProperties<T>.Property p); } /** Implementation of TypeSystem using JSTypes. */ private static class JSTypeSystem implements TypeSystem<JSType> { private final Set<JSType> invalidatingTypes; private JSTypeRegistry registry; public JSTypeSystem(AbstractCompiler compiler) { registry = compiler.getTypeRegistry(); invalidatingTypes = Sets.newHashSet( registry.getNativeType(JSTypeNative.ALL_TYPE), registry.getNativeType(JSTypeNative.NO_OBJECT_TYPE), registry.getNativeType(JSTypeNative.NO_TYPE), registry.getNativeType(JSTypeNative.FUNCTION_PROTOTYPE), registry.getNativeType(JSTypeNative.FUNCTION_INSTANCE_TYPE), registry.getNativeType(JSTypeNative.OBJECT_PROTOTYPE), registry.getNativeType(JSTypeNative.TOP_LEVEL_PROTOTYPE), registry.getNativeType(JSTypeNative.UNKNOWN_TYPE)); } @Override public void addInvalidatingType(JSType type) { checkState(!type.isUnionType()); invalidatingTypes.add(type); } @Override public StaticScope<JSType> getRootScope() { return null; } @Override public StaticScope<JSType> getFunctionScope(Node node) { return null; } @Override public JSType getType( StaticScope<JSType> scope, Node node, String prop) { if (node.getJSType() == null) { return registry.getNativeType(JSTypeNative.UNKNOWN_TYPE); } return node.getJSType(); } @Override public boolean isInvalidating

Closure, 120

<FILEB>
<CHANGES>
if (ref.getSymbol().getScope()!= ref.scope) {
return false;
}
<CHANGEE>
<FILEE>
<FILEB> return references.get(i); } } return null; } /** * @return Whether the variable is only assigned a value once for its * lifetime. */ boolean isAssignedOnceInLifetime() { Reference ref = getOneAndOnlyAssignment(); if (ref == null) { return false; } // Make sure this assignment is not in a loop. for (BasicBlock block = ref.getBasicBlock(); block != null; block = block.getParent()) { if (block.isFunction) { <CHANGES> <CHANGEE> break; } else if (block.isLoop) { return false; } } return true; } /** * @return The one and only assignment. Returns if there are 0 or 2+ * assignments. */ private Reference getOneAndOnlyAssignment() { <FILEE> <SCANS>objType != null && objType.getImplicitPrototype() != objType) { if (objType.hasOwnProperty(field)) { foundType = objType; } objType = objType.getImplicitPrototype(); } } // If the property does not exist on the referenced type but the original // type is an object type, see if any subtype has the property. if (foundType == null) { ObjectType maybeType = ObjectType.cast( registry.getGreatestSubtypeWithProperty(type, field)); // getGreatestSubtypeWithProperty does not guarantee that the property // is defined on the returned type, it just indicates that it might be, // so we have to double check. if (maybeType != null && maybeType.hasOwnProperty(field)) { foundType = maybeType; } } return foundType; } @Override public JSType getInstanceFromPrototype(JSType type) { if (type.isFunctionPrototypeType()) { ObjectType prototype = (ObjectType) type; FunctionType owner = prototype.getOwnerFunction(); if (owner.isConstructor() || owner.isInterface()) { return prototype.getOwnerFunction().getInstanceType(); } } return null; } @Override public void recordInterfaces(JSType type, JSType relatedType, DisambiguateProperties<JSType>.Property p) { ObjectType objType = ObjectType.cast(type); if (objType != null) { FunctionType constructor; if (objType.isFunctionType()) { constructor = objType.toMaybeFunctionType(); } else if (objType.isFunctionPrototypeType()) { constructor = objType.getOwnerFunction(); } else { constructor = objType.getConstructor(); } while (constructor != null) { for (ObjectType itype : constructor.getImplementedInterfaces()) { JSType top = getTypeWithProperty(p.name, itype); if (top != null) { p.addType(itype, top, relatedType); } else { recordInterfaces(itype, relatedType, p); } // If this interface invalidated this property, return now. if (p.skipRenaming) { return; } } if (constructor.isInterface() || constructor.isConstructor()) { constructor = constructor.getSuperClassConstructor(); } else { constructor = null; } } } } } /** Implementation of TypeSystem using concrete types. */ private static class ConcreteTypeSystem implements TypeSystem<ConcreteType> { private final TightenTypes tt; private int nextUniqueId; private CodingConvention codingConvention; private final Set<JSType> invalidatingTypes = Sets.newHashSet(); // An array of native types that are not tracked by type tightening, and // thus need to be added in if an unknown type is encountered. private static final JSTypeNative [] nativeTypes = new JSTypeNative[] { JSTypeNative.BOOLEAN_OBJECT_TYPE, JSTypeNative.NUMBER_OBJECT_TYPE, JSTypeNative.STRING_OBJECT_TYPE }; public ConcreteTypeSystem(TightenTypes tt, CodingConvention convention) { this.tt = tt; this.codingConvention = convention; } @Override public void addInvalidatingType(JSType type) { checkState(!type.isUnionType()); invalidatingTypes.add(type); } @Override public StaticScope<ConcreteType> getRootScope() { return tt.getTopScope(); } @Override public StaticScope<ConcreteType> getFunctionScope(Node decl) { ConcreteFunctionType func = tt.getConcreteFunction(decl); return (func != null) ? func.getScope() : (StaticScope<Concrete

Closure, 120

<FILEB>
<CHANGES>
if (ref.getSymbol().getScope()!= ref.scope) {
return false;
}
<CHANGEE>
<FILEE>
<FILEB> return references.get(i); } } return null; } /** * @return Whether the variable is only assigned a value once for its * lifetime. */ boolean isAssignedOnceInLifetime() { Reference ref = getOneAndOnlyAssignment(); if (ref == null) { return false; } // Make sure this assignment is not in a loop. for (BasicBlock block = ref.getBasicBlock(); block != null; block = block.getParent()) { if (block.isFunction) { <CHANGES> <CHANGEE> break; } else if (block.isLoop) { return false; } } return true; } /** * @return The one and only assignment. Returns if there are 0 or 2+ * assignments. */ private Reference getOneAndOnlyAssignment() { <FILEE> <SCANS>Type>) null; } @Override public ConcreteType getType( StaticScope<ConcreteType> scope, Node node, String prop) { if (scope != null) { ConcreteType c = tt.inferConcreteType( (TightenTypes.ConcreteScope) scope, node); return maybeAddAutoboxes(c, node, prop); } else { return null; } } /** * Add concrete types for autoboxing types if necessary. The concrete type * system does not track native types, like string, so add them if they are * present in the JSType for the node. */ private ConcreteType maybeAddAutoboxes( ConcreteType cType, Node node, String prop) { JSType jsType = node.getJSType(); if (jsType == null) { return cType; } else if (jsType.isUnknownType()) { for (JSTypeNative nativeType : nativeTypes) { ConcreteType concrete = tt.getConcreteInstance( tt.getTypeRegistry().getNativeObjectType(nativeType)); if (concrete != null && !concrete.getPropertyType(prop).isNone()) { cType = cType.unionWith(concrete); } } return cType; } return maybeAddAutoboxes(cType, jsType, prop); } private ConcreteType maybeAddAutoboxes( ConcreteType cType, JSType jsType, String prop) { jsType = jsType.restrictByNotNullOrUndefined(); if (jsType.isUnionType()) { for (JSType alt : jsType.toMaybeUnionType().getAlternates()) { cType = maybeAddAutoboxes(cType, alt, prop); } return cType; } else if (jsType.isEnumElementType()) { return maybeAddAutoboxes( cType, jsType.toMaybeEnumElementType().getPrimitiveType(), prop); } if (jsType.autoboxesTo() != null) { JSType autoboxed = jsType.autoboxesTo(); return cType.unionWith(tt.getConcreteInstance((ObjectType) autoboxed)); } else if (jsType.unboxesTo() != null) { return cType.unionWith(tt.getConcreteInstance((ObjectType) jsType)); } return cType; } @Override public boolean isInvalidatingType(ConcreteType type) { // We will disallow types on functions so that 'prototype' is not renamed. // TODO(user): Support properties on functions as well. return (type == null) || type.isAll() || type.isFunction() || (type.isInstance() && invalidatingTypes.contains(type.toInstance().instanceType)); } @Override public ImmutableSet<ConcreteType> getTypesToSkipForType(ConcreteType type) { return ImmutableSet.of(type); } @Override public boolean isTypeToSkip(ConcreteType type) { // Skip anonymous object literals and enum types. return type.isInstance() && !(type.toInstance().isFunctionPrototype() || type.toInstance().instanceType.isInstanceType()); } @Override public ConcreteType restrictByNotNullOrUndefined(ConcreteType type) { // These are not represented in concrete types. return type; } @Override public Iterable<ConcreteType> getTypeAlternatives(ConcreteType type) { if (type.isUnion()) { return ((ConcreteUnionType) type).getAlternatives(); } else { return null; } } @Override public ConcreteType getTypeWithProperty(String field, ConcreteType type) { if (type.isInstance()) { ConcreteInstanceType instanceType = (ConcreteInstanceType) type;

Closure, 120

<FILEB>
<CHANGES>
if (ref.getSymbol().getScope()!= ref.scope) {
return false;
}
<CHANGEE>
<FILEE>
<FILEB> return references.get(i); } } return null; } /** * @return Whether the variable is only assigned a value once for its * lifetime. */ boolean isAssignedOnceInLifetime() { Reference ref = getOneAndOnlyAssignment(); if (ref == null) { return false; } // Make sure this assignment is not in a loop. for (BasicBlock block = ref.getBasicBlock(); block != null; block = block.getParent()) { if (block.isFunction) { <CHANGES> <CHANGEE> break; } else if (block.isLoop) { return false; } } return true; } /** * @return The one and only assignment. Returns if there are 0 or 2+ * assignments. */ private Reference getOneAndOnlyAssignment() { <FILEE> <SCANS>/* * * ***** BEGIN LICENSE BLOCK ***** * Version: MPL 1.1/GPL 2.0 * * The contents of this file are subject to the Mozilla Public License Version * 1.1 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * http://www.mozilla.org/MPL/ * * Software distributed under the License is distributed on an "AS IS" basis, * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License * for the specific language governing rights and limitations under the * License. * * The Original Code is Rhino code, released * May 6, 1999. * * The Initial Developer of the Original Code is * Netscape Communications Corporation. * Portions created by the Initial Developer are Copyright (C) 1997-1999 * the Initial Developer. All Rights Reserved. * * Contributor(s): * Nick Santos * * Alternatively, the contents of this file may be used under the terms of * the GNU General Public License Version 2 or later (the "GPL"), in which * case the provisions of the GPL are applicable instead of those above. If * you wish to allow use of your version of this file only under the terms of * the GPL and not to allow others to use your version of this file under the * MPL, indicate your decision by deleting the provisions above and replacing * them with the notice and other provisions required by the GPL. If you do * not delete the provisions above, a recipient may use your version of this * file under either the MPL or the GPL. * * ***** END LICENSE BLOCK ***** */ package com.google.javascript.rhino.jstype; /** * Lookup references by the symbols that they refer to. * * @author nicksantos@google.com (Nick Santos) */ public interface StaticSymbolTable <S extends StaticSlot<JSType>, R extends StaticReference<JSType>> { /** * Returns the references that point to the given symbol. */ Iterable<R> getReferences(S symbol); /** * Returns the scope for a given symbol. */ StaticScope<JSType> getScope(S symbol); /** * Returns all variables in this symbol table. */ Iterable<S> getAllSymbols(); }

Closure, 120

<FILEB>
<CHANGES>
if (ref.getSymbol().getScope()!= ref.scope) {
return false;
}
<CHANGEE>
<FILEE>
<FILEB> return references.get(i); } } return null; } /** * @return Whether the variable is only assigned a value once for its * lifetime. */ boolean isAssignedOnceInLifetime() { Reference ref = getOneAndOnlyAssignment(); if (ref == null) { return false; } // Make sure this assignment is not in a loop. for (BasicBlock block = ref.getBasicBlock(); block != null; block = block.getParent()) { if (block.isFunction) { <CHANGES> <CHANGEE> break; } else if (block.isLoop) { return false; } } return true; } /** * @return The one and only assignment. Returns if there are 0 or 2+ * assignments. */ private Reference getOneAndOnlyAssignment() { <FILEE> <SCANS> // except for the name of the function itself. if (parent == null || !parent.isFunction() || n == parent.getFirstChild()) { if (NodeUtil.isVarDeclaration(n)) { renamer.addDeclaredName(n.getString()); } else if (NodeUtil.isFunctionDeclaration(n)) { Node nameNode = n.getFirstChild(); renamer.addDeclaredName(nameNode.getString()); } for (Node c = n.getFirstChild(); c != null; c = c.getNext()) { findDeclaredNames(c, n, renamer); } } } /** * Declared names renaming policy interface. */ interface Renamer { /** * Called when a declared name is found in the local current scope. */ void addDeclaredName(String name); /** * @return A replacement name, null if oldName is unknown or should not * be replaced. */ String getReplacementName(String oldName); /** * @return Whether the constant-ness of a name should be removed. */ boolean stripConstIfReplaced(); /** * @return A Renamer for a scope within the scope of the current Renamer. */ Renamer forChildScope(); } /** * Inverts the transformation by {@link ContextualRenamer}, when possible. */ static class ContextualRenameInverter implements ScopedCallback, CompilerPass { private final AbstractCompiler compiler; // The set of names referenced in the current scope. private Set<String> referencedNames = ImmutableSet.of(); // Stack reference sets. private Deque<Set<String>> referenceStack = new ArrayDeque<Set<String>>(); // Name are globally unique initially, so we don't need a per-scope map. private Map<String, List<Node>> nameMap = Maps.newHashMap(); private ContextualRenameInverter(AbstractCompiler compiler) { this.compiler = compiler; } @Override public void process(Node externs, Node js) { NodeTraversal.traverse(compiler, js, this); } public static String getOrginalName(String name) { int index = indexOfSeparator(name); return (index == -1) ? name : name.substring(0, index); } private static int indexOfSeparator(String name) { return name.lastIndexOf(ContextualRenamer.UNIQUE_ID_SEPARATOR); } private boolean containsSeparator(String name) { return name.indexOf(ContextualRenamer.UNIQUE_ID_SEPARATOR) != -1; } /** * Prepare a set for the new scope. */ @Override public void enterScope(NodeTraversal t) { if (t.inGlobalScope()) { return; } referenceStack.push(referencedNames); referencedNames = Sets.newHashSet(); } /** * Rename vars for the current scope, and merge any referenced * names into the parent scope reference set. */ @Override public void exitScope(NodeTraversal t) { if (t.inGlobalScope()) { return; } for (Iterator<Var> it = t.getScope().getVars(); it.hasNext();) { Var v = it.next(); handleScopeVar(v); } // Merge any names that were referenced but not declared in the current // scope. Set<String> current = referencedNames; referencedNames = referenceStack.pop(); // If there isn't anything left in the stack we will be going into the // global scope: don't try to build a set of referenced names for the // global scope. if (!referenceStack.isEmpty()) { referencedNames.addAll(current); }

Closure, 120

<FILEB>
<CHANGES>
if (ref.getSymbol().getScope()!= ref.scope) {
return false;
}
<CHANGEE>
<FILEE>
<FILEB> return references.get(i); } } return null; } /** * @return Whether the variable is only assigned a value once for its * lifetime. */ boolean isAssignedOnceInLifetime() { Reference ref = getOneAndOnlyAssignment(); if (ref == null) { return false; } // Make sure this assignment is not in a loop. for (BasicBlock block = ref.getBasicBlock(); block != null; block = block.getParent()) { if (block.isFunction) { <CHANGES> <CHANGEE> break; } else if (block.isLoop) { return false; } } return true; } /** * @return The one and only assignment. Returns if there are 0 or 2+ * assignments. */ private Reference getOneAndOnlyAssignment() { <FILEE> <SCANS>Info getJSDocInfo() { return referencedType.getJSDocInfo(); } @Override public void setJSDocInfo(JSDocInfo info) { if (referencedObjType != null) { referencedObjType.setJSDocInfo(info); } } @Override public void setPropertyJSDocInfo(String propertyName, JSDocInfo info) { if (referencedObjType != null) { referencedObjType.setPropertyJSDocInfo(propertyName, info); } } @Override public FunctionType getConstructor() { return referencedObjType == null ? null : referencedObjType.getConstructor(); } @Override public ImmutableList<JSType> getTemplateTypes() { return referencedObjType == null ? null : referencedObjType.getTemplateTypes(); } @Override public <T> T visit(Visitor<T> visitor) { return referencedType.visit(visitor); } @Override <T> T visit(RelationshipVisitor<T> visitor, JSType that) { return referencedType.visit(visitor, that); } @Override JSType resolveInternal(ErrorReporter t, StaticScope<JSType> scope) { setReferencedType(referencedType.resolve(t, scope)); return this; } @Override public String toDebugHashCodeString() { return "{proxy:" + referencedType.toDebugHashCodeString() + "}"; } @Override public JSType getTypeOfThis() { if (referencedObjType != null) { return referencedObjType.getTypeOfThis(); } return super.getTypeOfThis(); } @Override public JSType collapseUnion() { if (referencedType.isUnionType()) { return referencedType.collapseUnion(); } return this; } @Override public void matchConstraint(JSType constraint) { referencedType.matchConstraint(constraint); } @Override public TemplatizedType toMaybeTemplatizedType() { return referencedType.toMaybeTemplatizedType(); } @Override public TemplateType toMaybeTemplateType() { return referencedType.toMaybeTemplateType(); } @Override public boolean hasAnyTemplateTypesInternal() { return referencedType.hasAnyTemplateTypes(); } @Override public TemplateTypeMap getTemplateTypeMap() { return referencedType.getTemplateTypeMap(); } }

Closure, 120

<FILEB>
<CHANGES>
if (ref.getSymbol().getScope()!= ref.scope) {
return false;
}
<CHANGEE>
<FILEE>
<FILEB> return references.get(i); } } return null; } /** * @return Whether the variable is only assigned a value once for its * lifetime. */ boolean isAssignedOnceInLifetime() { Reference ref = getOneAndOnlyAssignment(); if (ref == null) { return false; } // Make sure this assignment is not in a loop. for (BasicBlock block = ref.getBasicBlock(); block != null; block = block.getParent()) { if (block.isFunction) { <CHANGES> <CHANGEE> break; } else if (block.isLoop) { return false; } } return true; } /** * @return The one and only assignment. Returns if there are 0 or 2+ * assignments. */ private Reference getOneAndOnlyAssignment() { <FILEE> <SCANS>()); } } else { // We have an assignment of the form "a = ...", so pull the // type off the "a". return normalizeClassType(lValue.getJSType()); } } else if (NodeUtil.isFunctionDeclaration(n) || parent.isName()) { return normalizeClassType(n.getJSType()); } return null; } /** * Normalize the type of a constructor, its instance, and its prototype * all down to the same type (the instance type). */ private JSType normalizeClassType(JSType type) { if (type == null || type.isUnknownType()) { return type; } else if (type.isNominalConstructor()) { return (type.toMaybeFunctionType()).getInstanceType(); } else if (type.isFunctionPrototypeType()) { FunctionType owner = ((ObjectType) type).getOwnerFunction(); if (owner.isConstructor()) { return owner.getInstanceType(); } } return type; } @Override public boolean shouldTraverse(NodeTraversal t, Node n, Node parent) { return true; } @Override public void visit(NodeTraversal t, Node n, Node parent) { switch (n.getType()) { case Token.NAME: checkNameDeprecation(t, n, parent); checkNameVisibility(t, n, parent); break; case Token.GETPROP: checkPropertyDeprecation(t, n, parent); checkPropertyVisibility(t, n, parent); checkConstantProperty(t, n); break; case Token.NEW: checkConstructorDeprecation(t, n, parent); break; case Token.FUNCTION: checkFinalClassOverrides(t, n, parent); break; } } /** * Checks the given NEW node to ensure that access restrictions are obeyed. */ private void checkConstructorDeprecation(NodeTraversal t, Node n, Node parent) { JSType type = n.getJSType(); if (type != null) { String deprecationInfo = getTypeDeprecationInfo(type); if (deprecationInfo != null && shouldEmitDeprecationWarning(t, n, parent)) { if (!deprecationInfo.isEmpty()) { compiler.report( t.makeError(n, DEPRECATED_CLASS_REASON, type.toString(), deprecationInfo)); } else { compiler.report( t.makeError(n, DEPRECATED_CLASS, type.toString())); } } } } /** * Checks the given NAME node to ensure that access restrictions are obeyed. */ private void checkNameDeprecation(NodeTraversal t, Node n, Node parent) { // Don't bother checking definitions or constructors. if (parent.isFunction() || parent.isVar() || parent.isNew()) { return; } Scope.Var var = t.getScope().getVar(n.getString()); JSDocInfo docInfo = var == null ? null : var.getJSDocInfo(); if (docInfo != null && docInfo.isDeprecated() && shouldEmitDeprecationWarning(t, n, parent)) { if (docInfo.getDeprecationReason() != null) { compiler.report( t.makeError(n, DEPRECATED_NAME_REASON, n.getString(), docInfo.getDeprecationReason())); } else { compiler.report( t.makeError(n, DEPRECATED_NAME, n.getString())); } } } /** * Checks the given GETPROP node to ensure that access restrictions are * obeyed. */ private void checkPropertyDeprecation(NodeTraversal t, Node n, Node parent) {

Closure, 120

<FILEB>
<CHANGES>
if (ref.getSymbol().getScope()!= ref.scope) {
return false;
}
<CHANGEE>
<FILEE>
<FILEB> return references.get(i); } } return null; } /** * @return Whether the variable is only assigned a value once for its * lifetime. */ boolean isAssignedOnceInLifetime() { Reference ref = getOneAndOnlyAssignment(); if (ref == null) { return false; } // Make sure this assignment is not in a loop. for (BasicBlock block = ref.getBasicBlock(); block != null; block = block.getParent()) { if (block.isFunction) { <CHANGES> <CHANGEE> break; } else if (block.isLoop) { return false; } } return true; } /** * @return The one and only assignment. Returns if there are 0 or 2+ * assignments. */ private Reference getOneAndOnlyAssignment() { <FILEE> <SCANS> // Don't bother checking constructors. if (parent.isNew()) { return; } ObjectType objectType = ObjectType.cast(dereference(n.getFirstChild().getJSType())); String propertyName = n.getLastChild().getString(); if (objectType != null) { String deprecationInfo = getPropertyDeprecationInfo(objectType, propertyName); if (deprecationInfo != null && shouldEmitDeprecationWarning(t, n, parent)) { if (!deprecationInfo.isEmpty()) { compiler.report( t.makeError(n, DEPRECATED_PROP_REASON, propertyName, validator.getReadableJSTypeName(n.getFirstChild(), true), deprecationInfo)); } else { compiler.report( t.makeError(n, DEPRECATED_PROP, propertyName, validator.getReadableJSTypeName(n.getFirstChild(), true))); } } } } /** * Determines whether the given name is visible in the current context. * @param t The current traversal. * @param name The name node. */ private void checkNameVisibility(NodeTraversal t, Node name, Node parent) { Var var = t.getScope().getVar(name.getString()); if (var != null) { JSDocInfo docInfo = var.getJSDocInfo(); if (docInfo != null) { // If a name is private, make sure that we're in the same file. Visibility visibility = docInfo.getVisibility(); if (visibility == Visibility.PRIVATE) { StaticSourceFile varSrc = var.getSourceFile(); StaticSourceFile refSrc = name.getStaticSourceFile(); if (varSrc != null && refSrc != null && !varSrc.getName().equals(refSrc.getName())) { if (docInfo.isConstructor() && isValidPrivateConstructorAccess(parent)) { return; } compiler.report( t.makeError(name, BAD_PRIVATE_GLOBAL_ACCESS, name.getString(), varSrc.getName())); } } } } } /** * Checks if a constructor is trying to override a final class. */ private void checkFinalClassOverrides(NodeTraversal t, Node fn, Node parent) { JSType type = fn.getJSType().toMaybeFunctionType(); if (type != null && type.isConstructor()) { JSType finalParentClass = getFinalParentClass(getClassOfMethod(fn, parent)); if (finalParentClass != null) { compiler.report( t.makeError(fn, EXTEND_FINAL_CLASS, type.getDisplayName(), finalParentClass.getDisplayName())); } } } /** * Determines whether the given property with @const tag got reassigned * @param t The current traversal. * @param getprop The getprop node. */ private void checkConstantProperty(NodeTraversal t, Node getprop) { // Check whether the property is modified Node parent = getprop.getParent(); boolean isDelete = parent.isDelProp(); if (!(NodeUtil.isAssignmentOp(parent) && parent.getFirstChild() == getprop) && !parent.isInc() && !parent.isDec() && !isDelete) { return; } ObjectType objectType = ObjectType.cast(dereference(getprop.getFirstChild().getJSType())); String propertyName = getprop.getLastChild().getString(); boolean isConstant = isPropertyDeclaredConstant(objectType, propertyName); // Check whether constant properties are reassigned if (isConstant) { if (isDelete) { compiler.report( t.makeError(getprop, CONST_PROPERTY_DELETED, propertyName)); return; } ObjectType oType = objectType

Closure, 120

<FILEB>
<CHANGES>
if (ref.getSymbol().getScope()!= ref.scope) {
return false;
}
<CHANGEE>
<FILEE>
<FILEB> return references.get(i); } } return null; } /** * @return Whether the variable is only assigned a value once for its * lifetime. */ boolean isAssignedOnceInLifetime() { Reference ref = getOneAndOnlyAssignment(); if (ref == null) { return false; } // Make sure this assignment is not in a loop. for (BasicBlock block = ref.getBasicBlock(); block != null; block = block.getParent()) { if (block.isFunction) { <CHANGES> <CHANGEE> break; } else if (block.isLoop) { return false; } } return true; } /** * @return The one and only assignment. Returns if there are 0 or 2+ * assignments. */ private Reference getOneAndOnlyAssignment() { <FILEE> <SCANS> if (docInfo.isConstructor() && isValidPrivateConstructorAccess(parent)) { return; } // private access is not allowed outside the file from a different // enclosing class. compiler.report( t.makeError(getprop, BAD_PRIVATE_PROPERTY_ACCESS, propertyName, validator.getReadableJSTypeName( getprop.getFirstChild(), true))); } else if (visibility == Visibility.PROTECTED) { // There are 3 types of legal accesses of a protected property: // 1) Accesses in the same file // 2) Overriding the property in a subclass // 3) Accessing the property from inside a subclass // The first two have already been checked for. if (currentClass == null || !currentClass.isSubtype(ownerType)) { compiler.report( t.makeError(getprop, BAD_PROTECTED_PROPERTY_ACCESS, propertyName, validator.getReadableJSTypeName( getprop.getFirstChild(), true))); } } } } } /** * Whether the given access of a private constructor is legal. * * For example, * new PrivateCtor_(); // not legal * PrivateCtor_.newInstance(); // legal * x instanceof PrivateCtor_ // legal * * This is a weird special case, because our visibility system is inherited * from Java, and JavaScript has no distinction between classes and * constructors like Java does. * * We may want to revisit this if we decide to make the restrictions tighter. */ private static boolean isValidPrivateConstructorAccess(Node parent) { return !parent.isNew(); } /** * Determines whether a deprecation warning should be emitted. * @param t The current traversal. * @param n The node which we are checking. * @param parent The parent of the node which we are checking. */ private boolean shouldEmitDeprecationWarning( NodeTraversal t, Node n, Node parent) { // In the global scope, there are only two kinds of accesses that should // be flagged for warnings: // 1) Calls of deprecated functions and methods. // 2) Instantiations of deprecated classes. // For now, we just let everything else by. if (t.inGlobalScope()) { if (!((parent.isCall() && parent.getFirstChild() == n) || n.isNew())) { return false; } } // We can always assign to a deprecated property, to keep it up to date. if (n.isGetProp() && n == parent.getFirstChild() && NodeUtil.isAssignmentOp(parent)) { return false; } return !canAccessDeprecatedTypes(t); } /** * Returns whether it's currently OK to access deprecated names and * properties. * * There are 3 exceptions when we're allowed to use a deprecated * type or property: * 1) When we're in a deprecated function. * 2) When we're in a deprecated class. * 3) When we're in a static method of a deprecated class. */ private boolean canAccessDeprecatedTypes(NodeTraversal t) { Node scopeRoot = t.getScopeRoot(); Node scopeRootParent = scopeRoot.getParent(); return // Case #1 (deprecatedDepth > 0) || // Case #2 (getTypeDeprecationInfo(t.getScope().getTypeOfThis()) != null) || // Case #3 (scopeRootParent != null && scopeRootParent.isAssign() && getTypeDeprecationInfo( getClassOfMethod(scopeRoot, scopeRootParent)) != null); } /** * Returns whether this is a function node annotated as deprecated.

Closure, 120

<FILEB>
<CHANGES>
if (ref.getSymbol().getScope()!= ref.scope) {
return false;
}
<CHANGEE>
<FILEE>
<FILEB> return references.get(i); } } return null; } /** * @return Whether the variable is only assigned a value once for its * lifetime. */ boolean isAssignedOnceInLifetime() { Reference ref = getOneAndOnlyAssignment(); if (ref == null) { return false; } // Make sure this assignment is not in a loop. for (BasicBlock block = ref.getBasicBlock(); block != null; block = block.getParent()) { if (block.isFunction) { <CHANGES> <CHANGEE> break; } else if (block.isLoop) { return false; } } return true; } /** * @return The one and only assignment. Returns if there are 0 or 2+ * assignments. */ private Reference getOneAndOnlyAssignment() { <FILEE> <SCANS>Range(refList, lastBefore, firstAfter); } private static class SourceRefRange { private final int lastBefore; private final int firstAfter; private final List<Reference> refList; SourceRefRange(List<Reference> refList, int lastBefore, int firstAfter) { this.lastBefore = Math.max(lastBefore, -1); this.firstAfter = Math.min(firstAfter, refList.size()); this.refList = refList; } /** Note that the returned list is backed by {@code refList}! */ List<Reference> refsBefore() { return refList.subList(0, lastBefore + 1); } /** Note that the returned list is backed by {@code refList}! */ List<Reference> refsAfter() { return refList.subList(firstAfter, refList.size()); } } /** * @param globalScope a new Global Scope to replace the scope of references * with. */ public void updateReferencesWithGlobalScope(Scope globalScope) { for (ReferenceCollection collection : refMap.values()) { List<Reference> newRefs = Lists.newArrayListWithCapacity(collection.references.size()); for (Reference ref : collection.references) { if (ref.getScope() != globalScope) { newRefs.add(ref.cloneWithNewScope(globalScope)); } else { newRefs.add(ref); } } collection.references = newRefs; } } /** * A CleanupPass implementation that will replace references to old Syntactic * Global Scopes generated in previous compile runs with references to the * Global Typed Scope. * * @author tylerg@google.com (Tyler Goodwin) */ static class GlobalVarRefCleanupPass implements HotSwapCompilerPass { private final AbstractCompiler compiler; public GlobalVarRefCleanupPass(AbstractCompiler compiler) { this.compiler = compiler; } @Override public void hotSwapScript(Node scriptRoot, Node originalRoot) { GlobalVarReferenceMap refMap = compiler.getGlobalVarReferences(); if (refMap != null) { refMap.updateReferencesWithGlobalScope(compiler.getTopScope()); } } @Override public void process(Node externs, Node root) { // GlobalVarRefCleanupPass should not do work during process. } } }

Closure, 120

<FILEB>
<CHANGES>
if (ref.getSymbol().getScope()!= ref.scope) {
return false;
}
<CHANGEE>
<FILEE>
<FILEB> return references.get(i); } } return null; } /** * @return Whether the variable is only assigned a value once for its * lifetime. */ boolean isAssignedOnceInLifetime() { Reference ref = getOneAndOnlyAssignment(); if (ref == null) { return false; } // Make sure this assignment is not in a loop. for (BasicBlock block = ref.getBasicBlock(); block != null; block = block.getParent()) { if (block.isFunction) { <CHANGES> <CHANGEE> break; } else if (block.isLoop) { return false; } } return true; } /** * @return The one and only assignment. Returns if there are 0 or 2+ * assignments. */ private Reference getOneAndOnlyAssignment() { <FILEE> <SCANS>Node); /** * In many JS libraries, the function that creates a delegate relationship * also adds properties to the delegator and delegate base. */ public void applyDelegateRelationship( ObjectType delegateSuperclass, ObjectType delegateBase, ObjectType delegator, FunctionType delegateProxy, FunctionType findDelegate); /** * @return the name of the delegate superclass. */ public String getDelegateSuperclassName(); /** * Checks for function calls that set the calling conventions on delegate * methods. */ public void checkForCallingConventionDefiningCalls( Node n, Map<String, String> delegateCallingConventions); /** * Defines the delegate proxy prototype properties. Their types depend on * properties of the delegate base methods. * * @param delegateProxyPrototypes List of delegate proxy prototypes. */ public void defineDelegateProxyPrototypeProperties( JSTypeRegistry registry, StaticScope<JSType> scope, List<ObjectType> delegateProxyPrototypes, Map<String, String> delegateCallingConventions); /** * Gets the name of the global object. */ public String getGlobalObject(); /** * A Bind instance or null. */ public Bind describeFunctionBind(Node n); /** * A Bind instance or null. * @param useTypeInfo If we believe type information is reliable enough * to use to figure out what the bind function is. */ public Bind describeFunctionBind(Node n, boolean useTypeInfo); /** Bind class */ public static class Bind { // The target of the bind action final Node target; // The node representing the "this" value, maybe null final Node thisValue; // The head of a Node list representing the parameters final Node parameters; public Bind(Node target, Node thisValue, Node parameters) { this.target = target; this.thisValue = thisValue; this.parameters = parameters; } /** * The number of parameters bound (not including the 'this' value). */ int getBoundParameterCount() { if (parameters == null) { return 0; } Node paramParent = parameters.getParent(); return paramParent.getChildCount() - paramParent.getIndexOfChild(parameters); } } /** * Whether this CALL function is testing for the existence of a property. */ public boolean isPropertyTestFunction(Node call); /** * Whether this GETPROP node is an alias for an object prototype. */ public boolean isPrototypeAlias(Node getProp); /** * Checks if the given method performs a object literal cast, and if it does, * returns information on the cast. By default, always returns null. Meant * to be overridden by subclasses. * * @param callNode A CALL node. */ public ObjectLiteralCast getObjectLiteralCast(Node callNode); /** * Gets a collection of all properties that are defined indirectly on global * objects. (For example, Closure defines superClass_ in the goog.inherits * call). */ public Collection<String> getIndirectlyDeclaredProperties(); /** * Returns the set of AssertionFunction. */ public Collection<AssertionFunctionSpec> getAssertionFunctions(); /** Specify the kind of inheritance */ static enum SubclassType { INHERITS, MIXIN } /** Record subclass relations */ static class SubclassRelationship { final SubclassType type; final String subclassName; final String superclassName; public SubclassRelationship(SubclassType type, Node subclassNode, Node superclassNode) { this.type = type; this.subclassName = subclassNode.getQualifiedName(); this.superclassName = superclassNode.getQualifiedName(); } } /** * Delegates provides a mechanism and

Closure, 120

<FILEB>
<CHANGES>
if (ref.getSymbol().getScope()!= ref.scope) {
return false;
}
<CHANGEE>
<FILEE>
<FILEB> return references.get(i); } } return null; } /** * @return Whether the variable is only assigned a value once for its * lifetime. */ boolean isAssignedOnceInLifetime() { Reference ref = getOneAndOnlyAssignment(); if (ref == null) { return false; } // Make sure this assignment is not in a loop. for (BasicBlock block = ref.getBasicBlock(); block != null; block = block.getParent()) { if (block.isFunction) { <CHANGES> <CHANGEE> break; } else if (block.isLoop) { return false; } } return true; } /** * @return The one and only assignment. Returns if there are 0 or 2+ * assignments. */ private Reference getOneAndOnlyAssignment() { <FILEE> <SCANS> getRestrictedTypeGivenToBooleanOutcome(boolean outcome) { // gather elements after restriction UnionTypeBuilder restricted = new UnionTypeBuilder(registry); for (JSType element : alternates) { restricted.addAlternate( element.getRestrictedTypeGivenToBooleanOutcome(outcome)); } return restricted.build(); } @Override public BooleanLiteralSet getPossibleToBooleanOutcomes() { BooleanLiteralSet literals = BooleanLiteralSet.EMPTY; for (JSType element : alternates) { literals = literals.union(element.getPossibleToBooleanOutcomes()); if (literals == BooleanLiteralSet.BOTH) { break; } } return literals; } @Override public TypePair getTypesUnderEquality(JSType that) { UnionTypeBuilder thisRestricted = new UnionTypeBuilder(registry); UnionTypeBuilder thatRestricted = new UnionTypeBuilder(registry); for (JSType element : alternates) { TypePair p = element.getTypesUnderEquality(that); if (p.typeA != null) { thisRestricted.addAlternate(p.typeA); } if (p.typeB != null) { thatRestricted.addAlternate(p.typeB); } } return new TypePair( thisRestricted.build(), thatRestricted.build()); } @Override public TypePair getTypesUnderInequality(JSType that) { UnionTypeBuilder thisRestricted = new UnionTypeBuilder(registry); UnionTypeBuilder thatRestricted = new UnionTypeBuilder(registry); for (JSType element : alternates) { TypePair p = element.getTypesUnderInequality(that); if (p.typeA != null) { thisRestricted.addAlternate(p.typeA); } if (p.typeB != null) { thatRestricted.addAlternate(p.typeB); } } return new TypePair( thisRestricted.build(), thatRestricted.build()); } @Override public TypePair getTypesUnderShallowInequality(JSType that) { UnionTypeBuilder thisRestricted = new UnionTypeBuilder(registry); UnionTypeBuilder thatRestricted = new UnionTypeBuilder(registry); for (JSType element : alternates) { TypePair p = element.getTypesUnderShallowInequality(that); if (p.typeA != null) { thisRestricted.addAlternate(p.typeA); } if (p.typeB != null) { thatRestricted.addAlternate(p.typeB); } } return new TypePair( thisRestricted.build(), thatRestricted.build()); } @Override public <T> T visit(Visitor<T> visitor) { return visitor.caseUnionType(this); } @Override <T> T visit(RelationshipVisitor<T> visitor, JSType that) { return visitor.caseUnionType(this, that); } @Override JSType resolveInternal(ErrorReporter t, StaticScope<JSType> scope) { setResolvedTypeInternal(this); // for circularly defined types. // Just resolve the alternates, but do not update as that breaks some error // reporting cases. for (JSType alternate : alternates) { alternate.resolve(t, scope); } // Ensure the union is in a normalized state. rebuildAlternates(); return this; } @Override public String toDebugHashCodeString() { List<String> hashCodes = Lists.newArrayList(); for (JSType a : alternates) { hashCodes.add(a.toDebugHashCodeString()); } return "{(" + Joiner.on(",").join(hashCodes) + ")}"; } @Override

Closure, 120

<FILEB>
<CHANGES>
if (ref.getSymbol().getScope()!= ref.scope) {
return false;
}
<CHANGEE>
<FILEE>
<FILEB> return references.get(i); } } return null; } /** * @return Whether the variable is only assigned a value once for its * lifetime. */ boolean isAssignedOnceInLifetime() { Reference ref = getOneAndOnlyAssignment(); if (ref == null) { return false; } // Make sure this assignment is not in a loop. for (BasicBlock block = ref.getBasicBlock(); block != null; block = block.getParent()) { if (block.isFunction) { <CHANGES> <CHANGEE> break; } else if (block.isLoop) { return false; } } return true; } /** * @return The one and only assignment. Returns if there are 0 or 2+ * assignments. */ private Reference getOneAndOnlyAssignment() { <FILEE> <SCANS> { return null; } @Override public String getReferenceName() { return null; } @Override public boolean matchesNumberContext() { return true; } @Override public boolean matchesObjectContext() { return true; } @Override public boolean matchesStringContext() { return true; } @Override public int hashCode() { return System.identityHashCode(this); } @Override boolean defineProperty(String propertyName, JSType type, boolean inferred, Node propertyNode) { // nothing, all properties are defined return true; } @Override public boolean removeProperty(String name) { return false; } @Override public void setPropertyJSDocInfo(String propertyName, JSDocInfo info) { // Do nothing, specific properties do not have JSDocInfo. } @Override public <T> T visit(Visitor<T> visitor) { return visitor.caseNoObjectType(); } @Override <T> T visit(RelationshipVisitor<T> visitor, JSType that) { return visitor.caseNoObjectType(that); } @Override String toStringHelper(boolean forAnnotations) { return forAnnotations ? "?" : "NoObject"; } @Override public FunctionType getConstructor() { return null; } @Override JSType resolveInternal(ErrorReporter t, StaticScope<JSType> scope) { return this; } }

Closure, 120

<FILEB>
<CHANGES>
if (ref.getSymbol().getScope()!= ref.scope) {
return false;
}
<CHANGEE>
<FILEE>
<FILEB> return references.get(i); } } return null; } /** * @return Whether the variable is only assigned a value once for its * lifetime. */ boolean isAssignedOnceInLifetime() { Reference ref = getOneAndOnlyAssignment(); if (ref == null) { return false; } // Make sure this assignment is not in a loop. for (BasicBlock block = ref.getBasicBlock(); block != null; block = block.getParent()) { if (block.isFunction) { <CHANGES> <CHANGEE> break; } else if (block.isLoop) { return false; } } return true; } /** * @return The one and only assignment. Returns if there are 0 or 2+ * assignments. */ private Reference getOneAndOnlyAssignment() { <FILEE> <SCANS>Registry; firstLink = this; nextLink = null; } /** * Appends a link to {@code this}, returning the updated last link. * <p> * The pattern {@code new X().append(new Y())...append(new Z())} forms a * chain starting with X, then Y, then ... Z. * @param lastLink a chainable interpreter, with no next link * @return the updated last link */ public ChainableReverseAbstractInterpreter append( ChainableReverseAbstractInterpreter lastLink) { Preconditions.checkArgument(lastLink.nextLink == null); this.nextLink = lastLink; lastLink.firstLink = this.firstLink; return lastLink; } /** * Gets the first link of this chain. */ public ChainableReverseAbstractInterpreter getFirst() { return firstLink; } /** * Calculates the preciser scope starting with the first link. */ protected FlowScope firstPreciserScopeKnowingConditionOutcome(Node condition, FlowScope blindScope, boolean outcome) { return firstLink.getPreciserScopeKnowingConditionOutcome( condition, blindScope, outcome); } /** * Delegates the calculation of the preciser scope to the next link. * If there is no next link, returns the blind scope. */ protected FlowScope nextPreciserScopeKnowingConditionOutcome(Node condition, FlowScope blindScope, boolean outcome) { return nextLink != null ? nextLink.getPreciserScopeKnowingConditionOutcome( condition, blindScope, outcome) : blindScope; } /** * Returns the type of a node in the given scope if the node corresponds to a * name whose type is capable of being refined. * @return The current type of the node if it can be refined, null otherwise. */ protected JSType getTypeIfRefinable(Node node, FlowScope scope) { switch (node.getType()) { case Token.NAME: StaticSlot<JSType> nameVar = scope.getSlot(node.getString()); if (nameVar != null) { JSType nameVarType = nameVar.getType(); if (nameVarType == null) { nameVarType = node.getJSType(); } return nameVarType; } return null; case Token.GETPROP: String qualifiedName = node.getQualifiedName(); if (qualifiedName == null) { return null; } StaticSlot<JSType> propVar = scope.getSlot(qualifiedName); JSType propVarType = null; if (propVar != null) { propVarType = propVar.getType(); } if (propVarType == null) { propVarType = node.getJSType(); } if (propVarType == null) { propVarType = getNativeType(UNKNOWN_TYPE); } return propVarType; } return null; } /** * Declares a refined type in {@code scope} for the name represented by * {@code node}. It must be possible to refine the type of the given node in * the given scope, as determined by {@link #getTypeIfRefinable}. */ protected void declareNameInScope(FlowScope scope, Node node, JSType type) { switch (node.getType()) { case Token.NAME: scope.inferSlotType(node.getString(), type); break; case Token.GETPROP: String qualifiedName = node.getQualifiedName(); Preconditions.checkNotNull(qualifiedName); JSType origType = node.getJSType(); origType = origType == null ? getNativeType(UNKNOWN_TYPE) :

Closure, 120

<FILEB>
<CHANGES>
if (ref.getSymbol().getScope()!= ref.scope) {
return false;
}
<CHANGEE>
<FILEE>
<FILEB> return references.get(i); } } return null; } /** * @return Whether the variable is only assigned a value once for its * lifetime. */ boolean isAssignedOnceInLifetime() { Reference ref = getOneAndOnlyAssignment(); if (ref == null) { return false; } // Make sure this assignment is not in a loop. for (BasicBlock block = ref.getBasicBlock(); block != null; block = block.getParent()) { if (block.isFunction) { <CHANGES> <CHANGEE> break; } else if (block.isLoop) { return false; } } return true; } /** * @return The one and only assignment. Returns if there are 0 or 2+ * assignments. */ private Reference getOneAndOnlyAssignment() { <FILEE> <SCANS> origType; scope.inferQualifiedSlot(node, qualifiedName, origType, type); break; case Token.THIS: // "this" references aren't currently modeled in the CFG. break; default: throw new IllegalArgumentException("Node cannot be refined. \n" + node.toStringTree()); } } /** * @see #getRestrictedWithoutUndefined(JSType) */ private final Visitor<JSType> restrictUndefinedVisitor = new Visitor<JSType>() { @Override public JSType caseEnumElementType(EnumElementType enumElementType) { JSType type = enumElementType.getPrimitiveType().visit(this); if (type != null && enumElementType.getPrimitiveType().isEquivalentTo(type)) { return enumElementType; } else { return type; } } @Override public JSType caseAllType() { return typeRegistry.createUnionType(OBJECT_TYPE, NUMBER_TYPE, STRING_TYPE, BOOLEAN_TYPE, NULL_TYPE); } @Override public JSType caseNoObjectType() { return getNativeType(NO_OBJECT_TYPE); } @Override public JSType caseNoType() { return getNativeType(NO_TYPE); } @Override public JSType caseBooleanType() { return getNativeType(BOOLEAN_TYPE); } @Override public JSType caseFunctionType(FunctionType type) { return type; } @Override public JSType caseNullType() { return getNativeType(NULL_TYPE); } @Override public JSType caseNumberType() { return getNativeType(NUMBER_TYPE); } @Override public JSType caseObjectType(ObjectType type) { return type; } @Override public JSType caseStringType() { return getNativeType(STRING_TYPE); } @Override public JSType caseUnionType(UnionType type) { return type.getRestrictedUnion(getNativeType(VOID_TYPE)); } @Override public JSType caseUnknownType() { return getNativeType(UNKNOWN_TYPE); } @Override public JSType caseVoidType() { return null; } @Override public JSType caseTemplatizedType(TemplatizedType type) { return caseObjectType(type); } @Override public JSType caseTemplateType(TemplateType templateType) { return caseObjectType(templateType); } }; /** * @see #getRestrictedWithoutNull(JSType) */ private final Visitor<JSType> restrictNullVisitor = new Visitor<JSType>() { @Override public JSType caseEnumElementType(EnumElementType enumElementType) { JSType type = enumElementType.getPrimitiveType().visit(this); if (type != null && enumElementType.getPrimitiveType().isEquivalentTo(type)) { return enumElementType; } else { return type; } } @Override public JSType caseAllType() { return typeRegistry.createUnionType(OBJECT_TYPE, NUMBER_TYPE, STRING_TYPE, BOOLEAN_TYPE, VOID_TYPE); } @Override public JSType caseNoObjectType() { return getNativeType(NO_OBJECT_TYPE); } @Override public JSType caseNoType() { return getNativeType(NO_TYPE); } @Override public JSType caseBooleanType() { return getNativeType(BOOLEAN_TYPE); } @Override public JSType caseFunctionType(FunctionType type) { return type; } @Override public JSType caseNullType() { return null; } @Override public JSType caseNumberType() { return getNativeType

Closure, 120

<FILEB>
<CHANGES>
if (ref.getSymbol().getScope()!= ref.scope) {
return false;
}
<CHANGEE>
<FILEE>
<FILEB> return references.get(i); } } return null; } /** * @return Whether the variable is only assigned a value once for its * lifetime. */ boolean isAssignedOnceInLifetime() { Reference ref = getOneAndOnlyAssignment(); if (ref == null) { return false; } // Make sure this assignment is not in a loop. for (BasicBlock block = ref.getBasicBlock(); block != null; block = block.getParent()) { if (block.isFunction) { <CHANGES> <CHANGEE> break; } else if (block.isLoop) { return false; } } return true; } /** * @return The one and only assignment. Returns if there are 0 or 2+ * assignments. */ private Reference getOneAndOnlyAssignment() { <FILEE> <SCANS> type may change over time (as more code is discovered), whereas a * declared type is a static contract that must be matched. */ private final boolean typeInferred; /** Input source */ final CompilerInput input; /** * The index at which the var is declared. e..g if it's 0, it's the first * declared variable in that scope */ final int index; /** The enclosing scope */ final Scope scope; /** @see #isMarkedEscaped */ private boolean markedEscaped = false; /** @see #isMarkedAssignedExactlyOnce */ private boolean markedAssignedExactlyOnce = false; /** * Creates a variable. * * @param inferred whether its type is inferred (as opposed to declared) */ private Var(boolean inferred, String name, Node nameNode, JSType type, Scope scope, int index, CompilerInput input) { this.name = name; this.nameNode = nameNode; this.type = type; this.scope = scope; this.index = index; this.input = input; this.typeInferred = inferred; } /** * Gets the name of the variable. */ @Override public String getName() { return name; } /** * Gets the node for the name of the variable. */ @Override public Node getNode() { return nameNode; } CompilerInput getInput() { return input; } @Override public StaticSourceFile getSourceFile() { return nameNode.getStaticSourceFile(); } @Override public Var getSymbol() { return this; } @Override public Var getDeclaration() { return nameNode == null ? null : this; } /** * Gets the parent of the name node. */ public Node getParentNode() { return nameNode == null ? null : nameNode.getParent(); } /** * Whether this is a bleeding function (an anonymous named function * that bleeds into the inner scope). */ public boolean isBleedingFunction() { return NodeUtil.isFunctionExpression(getParentNode()); } /** * Gets the scope where this variable is declared. */ Scope getScope() { return scope; } /** * Returns whether this is a global variable. */ public boolean isGlobal() { return scope.isGlobal(); } /** * Returns whether this is a local variable. */ public boolean isLocal() { return scope.isLocal(); } /** * Returns whether this is defined in an extern file. */ boolean isExtern() { return input == null || input.isExtern(); } /** * Returns {@code true} if the variable is declared as a constant, * based on the value reported by {@code NodeUtil}. */ public boolean isConst() { return nameNode != null && NodeUtil.isConstantName(nameNode); } /** * Returns {@code true} if the variable is declared as a define. * A variable is a define if it is annotated by {@code @define}. */ public boolean isDefine() { JSDocInfo info = getJSDocInfo(); return info != null && info.isDefine(); } public Node getInitialValue() { return NodeUtil.getRValueOfLValue(nameNode); } /** * Gets this variable's type. To know whether this type has been inferred, * see {@code #isTypeInferred()}. */ @Override public JSType getType() { return type; } /** * Returns the name node that produced this variable. */ public Node getNameNode() { return nameNode;

Closure, 120

<FILEB>
<CHANGES>
if (ref.getSymbol().getScope()!= ref.scope) {
return false;
}
<CHANGEE>
<FILEE>
<FILEB> return references.get(i); } } return null; } /** * @return Whether the variable is only assigned a value once for its * lifetime. */ boolean isAssignedOnceInLifetime() { Reference ref = getOneAndOnlyAssignment(); if (ref == null) { return false; } // Make sure this assignment is not in a loop. for (BasicBlock block = ref.getBasicBlock(); block != null; block = block.getParent()) { if (block.isFunction) { <CHANGES> <CHANGEE> break; } else if (block.isLoop) { return false; } } return true; } /** * @return The one and only assignment. Returns if there are 0 or 2+ * assignments. */ private Reference getOneAndOnlyAssignment() { <FILEE> <SCANS> } /** * Gets the JSDocInfo for the variable. */ @Override public JSDocInfo getJSDocInfo() { return nameNode == null ? null : NodeUtil.getBestJSDocInfo(nameNode); } /** * Sets this variable's type. * @throws IllegalStateException if the variable's type is not inferred */ void setType(JSType type) { Preconditions.checkState(isTypeInferred()); this.type = type; } /** * Resolve this variable's type. */ void resolveType(ErrorReporter errorReporter) { if (type != null) { type = type.resolve(errorReporter, scope); } } /** * Returns whether this variable's type is inferred. To get the variable's * type, see {@link #getType()}. */ @Override public boolean isTypeInferred() { return typeInferred; } public String getInputName() { if (input == null) { return "<non-file>"; } return input.getName(); } public boolean isNoShadow() { JSDocInfo info = getJSDocInfo(); return info != null && info.isNoShadow(); } @Override public boolean equals(Object other) { if (!(other instanceof Var)) { return false; } Var otherVar = (Var) other; return otherVar.nameNode == nameNode; } @Override public int hashCode() { return nameNode.hashCode(); } @Override public String toString() { return "Scope.Var " + name + "{" + type + "}"; } /** * Record that this is escaped by an inner scope. * * In other words, it's assigned in an inner scope so that it's much harder * to make assertions about its value at a given point. */ void markEscaped() { markedEscaped = true; } /** * Whether this is escaped by an inner scope. * Notice that not all scope creators record this information. */ boolean isMarkedEscaped() { return markedEscaped; } /** * Record that this is assigned exactly once.. * * In other words, it's assigned in an inner scope so that it's much harder * to make assertions about its value at a given point. */ void markAssignedExactlyOnce() { markedAssignedExactlyOnce = true; } /** * Whether this is assigned exactly once. * Notice that not all scope creators record this information. */ boolean isMarkedAssignedExactlyOnce() { return markedAssignedExactlyOnce; } } /** * A special subclass of Var used to distinguish "arguments" in the current * scope. */ // TODO(johnlenz): Include this the list of Vars for the scope. public static class Arguments extends Var { Arguments(Scope scope) { super( false, // no inferred "arguments", // always arguments null, // no declaration node // TODO(johnlenz): provide the type of "Arguments". null, // no type info scope, -1, // no variable index null // input ); } @Override public boolean equals(Object other) { if (!(other instanceof Arguments)) { return false; } Arguments otherVar = (Arguments) other; return otherVar.scope.getRootNode() == scope.getRootNode(); } @Override public int hashCode() { return System.identityHashCode(this); } } /** * Creates a Scope given the parent Scope and the root node of the scope. * @param parent The parent Scope. Cannot be null. *

Closure, 120

<FILEB>
<CHANGES>
if (ref.getSymbol().getScope()!= ref.scope) {
return false;
}
<CHANGEE>
<FILEE>
<FILEB> return references.get(i); } } return null; } /** * @return Whether the variable is only assigned a value once for its * lifetime. */ boolean isAssignedOnceInLifetime() { Reference ref = getOneAndOnlyAssignment(); if (ref == null) { return false; } // Make sure this assignment is not in a loop. for (BasicBlock block = ref.getBasicBlock(); block != null; block = block.getParent()) { if (block.isFunction) { <CHANGES> <CHANGEE> break; } else if (block.isLoop) { return false; } } return true; } /** * @return The one and only assignment. Returns if there are 0 or 2+ * assignments. */ private Reference getOneAndOnlyAssignment() { <FILEE> <SCANS> be used when the compiler optimizes out * a variable and removes it from the scope. */ void undeclare(Var var) { Preconditions.checkState(var.scope == this); Preconditions.checkState(vars.get(var.name) == var); vars.remove(var.name); } @Override public Var getSlot(String name) { return getVar(name); } @Override public Var getOwnSlot(String name) { return vars.get(name); } /** * Returns the variable, may be null */ public Var getVar(String name) { Var var = vars.get(name); if (var != null) { return var; } else if (parent != null) { // Recurse up the parent Scope return parent.getVar(name); } else { return null; } } /** * Get a unique VAR object to represents "arguments" within this scope */ public Var getArgumentsVar() { if (arguments == null) { arguments = new Arguments(this); } return arguments; } /** * Returns true if a variable is declared. */ public boolean isDeclared(String name, boolean recurse) { Scope scope = this; if (scope.vars.containsKey(name)) { return true; } if (scope.parent != null && recurse) { return scope.parent.isDeclared(name, recurse); } return false; } /** * Return an iterator over all of the variables declared in this scope. */ public Iterator<Var> getVars() { return vars.values().iterator(); } /** * Return an iterable over all of the variables declared in this scope. */ Iterable<Var> getVarIterable() { return vars.values(); } @Override public Iterable<Var> getReferences(Var var) { return ImmutableList.of(var); } @Override public StaticScope<JSType> getScope(Var var) { return var.scope; } @Override public Iterable<Var> getAllSymbols() { return Collections.unmodifiableCollection(vars.values()); } /** * Returns number of variables in this scope */ public int getVarCount() { return vars.size(); } /** * Returns whether this is the global scope. */ public boolean isGlobal() { return parent == null; } /** * Returns whether this is a local scope (i.e. not the global scope). */ public boolean isLocal() { return parent != null; } /** * Gets all variables declared with "var" but without declared types attached. */ public Iterator<Var> getDeclarativelyUnboundVarsWithoutTypes() { return Iterators.filter( getVars(), DECLARATIVELY_UNBOUND_VARS_WITHOUT_TYPES); } static interface TypeResolver { void resolveTypes(); } private TypeResolver typeResolver; /** Resolve all type references. Only used on typed scopes. */ void resolveTypes() { if (typeResolver != null) { typeResolver.resolveTypes(); typeResolver = null; } } void setTypeResolver(TypeResolver resolver) { this.typeResolver = resolver; } }

Closure, 120

<FILEB>
<CHANGES>
if (ref.getSymbol().getScope()!= ref.scope) {
return false;
}
<CHANGEE>
<FILEE>
<FILEB> return references.get(i); } } return null; } /** * @return Whether the variable is only assigned a value once for its * lifetime. */ boolean isAssignedOnceInLifetime() { Reference ref = getOneAndOnlyAssignment(); if (ref == null) { return false; } // Make sure this assignment is not in a loop. for (BasicBlock block = ref.getBasicBlock(); block != null; block = block.getParent()) { if (block.isFunction) { <CHANGES> <CHANGEE> break; } else if (block.isLoop) { return false; } } return true; } /** * @return The one and only assignment. Returns if there are 0 or 2+ * assignments. */ private Reference getOneAndOnlyAssignment() { <FILEE> <SCANS>/* * * ***** BEGIN LICENSE BLOCK ***** * Version: MPL 1.1/GPL 2.0 * * The contents of this file are subject to the Mozilla Public License Version * 1.1 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * http://www.mozilla.org/MPL/ * * Software distributed under the License is distributed on an "AS IS" basis, * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License * for the specific language governing rights and limitations under the * License. * * The Original Code is Rhino code, released * May 6, 1999. * * The Initial Developer of the Original Code is * Netscape Communications Corporation. * Portions created by the Initial Developer are Copyright (C) 1997-1999 * the Initial Developer. All Rights Reserved. * * Contributor(s): * Bob Jervis * Google Inc. * * Alternatively, the contents of this file may be used under the terms of * the GNU General Public License Version 2 or later (the "GPL"), in which * case the provisions of the GPL are applicable instead of those above. If * you wish to allow use of your version of this file only under the terms of * the GPL and not to allow others to use your version of this file under the * MPL, indicate your decision by deleting the provisions above and replacing * them with the notice and other provisions required by the GPL. If you do * not delete the provisions above, a recipient may use your version of this * file under either the MPL or the GPL. * * ***** END LICENSE BLOCK ***** */ package com.google.javascript.rhino.jstype; import static com.google.javascript.rhino.jstype.TernaryValue.UNKNOWN; import com.google.javascript.rhino.ErrorReporter; /** * All type, representing all values. */ public final class AllType extends JSType { private static final long serialVersionUID = 1L; AllType(JSTypeRegistry registry) { super(registry); } @Override public boolean isAllType() { return true; } @Override public boolean matchesStringContext() { // Be lenient. return true; } @Override public boolean matchesObjectContext() { // Be lenient. return true; } @Override public boolean canBeCalled() { return false; } @Override public TernaryValue testForEquality(JSType that) { return UNKNOWN; } @Override String toStringHelper(boolean forAnnotations) { return "*"; } @Override public String getDisplayName() { return "<Any Type>"; } @Override public boolean hasDisplayName() { return true; } @Override public <T> T visit(Visitor<T> visitor) { return visitor.caseAllType(); } @Override <T> T visit(RelationshipVisitor<T> visitor, JSType that) { return visitor.caseAllType(that); } @Override public BooleanLiteralSet getPossibleToBooleanOutcomes() { return BooleanLiteralSet.BOTH; } @Override JSType resolveInternal(ErrorReporter t, StaticScope<JSType> scope) { return this; } }

Closure, 120

<FILEB>
<CHANGES>
if (ref.getSymbol().getScope()!= ref.scope) {
return false;
}
<CHANGEE>
<FILEE>
<FILEB> return references.get(i); } } return null; } /** * @return Whether the variable is only assigned a value once for its * lifetime. */ boolean isAssignedOnceInLifetime() { Reference ref = getOneAndOnlyAssignment(); if (ref == null) { return false; } // Make sure this assignment is not in a loop. for (BasicBlock block = ref.getBasicBlock(); block != null; block = block.getParent()) { if (block.isFunction) { <CHANGES> <CHANGEE> break; } else if (block.isLoop) { return false; } } return true; } /** * @return The one and only assignment. Returns if there are 0 or 2+ * assignments. */ private Reference getOneAndOnlyAssignment() { <FILEE> <SCANS> primitiveType.testForEquality(that); } /** * This predicate determines whether objects of this type can have the null * value, and therefore can appear in contexts where null is expected. * * @return true for everything but Number and Boolean types. */ @Override public boolean isNullable() { return primitiveType.isNullable(); } @Override public boolean isNominalType() { return hasReferenceName(); } /** * If this is equal to a NamedType object, its hashCode must be equal * to the hashCode of the NamedType object. */ @Override public int hashCode() { if (hasReferenceName()) { return getReferenceName().hashCode(); } else { return super.hashCode(); } } @Override String toStringHelper(boolean forAnnotations) { return forAnnotations ? primitiveType.toString() : (getReferenceName() + ".<" + primitiveType + ">"); } @Override public String getReferenceName() { return name; } @Override public boolean hasReferenceName() { return true; } @Override public boolean isSubtype(JSType that) { if (JSType.isSubtypeHelper(this, that)) { return true; } else { return primitiveType.isSubtype(that); } } @Override public <T> T visit(Visitor<T> visitor) { return visitor.caseEnumElementType(this); } @Override <T> T visit(RelationshipVisitor<T> visitor, JSType that) { return visitor.caseEnumElementType(this, that); } @Override boolean defineProperty(String propertyName, JSType type, boolean inferred, Node propertyNode) { // nothing return true; } @Override public ObjectType getImplicitPrototype() { return null; } @Override public JSType findPropertyType(String propertyName) { return primitiveType.findPropertyType(propertyName); } @Override public FunctionType getConstructor() { return primitiveObjectType == null ? null : primitiveObjectType.getConstructor(); } @Override public JSType autoboxesTo() { return primitiveType.autoboxesTo(); } /** * Gets the primitive type of this enum element. */ public JSType getPrimitiveType() { return primitiveType; } /** * Returns the infimum of a enum element type and another type, or null * if the infimum is empty. * * This can be a little bit weird. For example, suppose you have an enum * of {(string|number)}, and you want the greatest subtype of the enum * and a {number}. * * The infimum is non-empty. But at the same time, we don't really have * a name for this infimum. It's equivalent to "elements of this enum that * are numbers". * * The best we can do is make up a new type. This is similar to what * we do in UnionType#meet, which kind-of-sort-of makes sense, because * an EnumElementType is a union of instances of a type. */ JSType meet(JSType that) { JSType meetPrimitive = primitiveType.getGreatestSubtype(that); if (meetPrimitive.isEmptyType()) { return null; } else { return new EnumElementType(registry, meetPrimitive, name); } } @Override JSType resolveInternal(ErrorReporter t, StaticScope<JSType> scope) { primitiveType = primitiveType.resolve(t, scope); primitiveObjectType = ObjectType.cast(primitiveType); return this; } }

Closure, 120

<FILEB>
<CHANGES>
if (ref.getSymbol().getScope()!= ref.scope) {
return false;
}
<CHANGEE>
<FILEE>
<FILEB> return references.get(i); } } return null; } /** * @return Whether the variable is only assigned a value once for its * lifetime. */ boolean isAssignedOnceInLifetime() { Reference ref = getOneAndOnlyAssignment(); if (ref == null) { return false; } // Make sure this assignment is not in a loop. for (BasicBlock block = ref.getBasicBlock(); block != null; block = block.getParent()) { if (block.isFunction) { <CHANGES> <CHANGEE> break; } else if (block.isLoop) { return false; } } return true; } /** * @return The one and only assignment. Returns if there are 0 or 2+ * assignments. */ private Reference getOneAndOnlyAssignment() { <FILEE> <SCANS> private final CodingConvention codingConvention; private final JSTypeRegistry typeRegistry; private final Node errorRoot; private final String sourceName; private final Scope scope; private FunctionContents contents = UnknownFunctionContents.get(); private JSType returnType = null; private boolean returnTypeInferred = false; private List<ObjectType> implementedInterfaces = null; private List<ObjectType> extendedInterfaces = null; private ObjectType baseType = null; private JSType thisType = null; private boolean isConstructor = false; private boolean makesStructs = false; private boolean makesDicts = false; private boolean isInterface = false; private Node parametersNode = null; private ImmutableList<TemplateType> templateTypeNames = ImmutableList.of(); // TODO(johnlenz): verify we want both template and class template lists instead of a unified // list. private ImmutableList<TemplateType> classTemplateTypeNames = ImmutableList.of(); static final DiagnosticType EXTENDS_WITHOUT_TYPEDEF = DiagnosticType.warning( "JSC_EXTENDS_WITHOUT_TYPEDEF", "@extends used without @constructor or @interface for {0}"); static final DiagnosticType EXTENDS_NON_OBJECT = DiagnosticType.warning( "JSC_EXTENDS_NON_OBJECT", "{0} @extends non-object type {1}"); static final DiagnosticType RESOLVED_TAG_EMPTY = DiagnosticType.warning( "JSC_RESOLVED_TAG_EMPTY", "Could not resolve type in {0} tag of {1}"); static final DiagnosticType IMPLEMENTS_WITHOUT_CONSTRUCTOR = DiagnosticType.warning( "JSC_IMPLEMENTS_WITHOUT_CONSTRUCTOR", "@implements used without @constructor or @interface for {0}"); static final DiagnosticType CONSTRUCTOR_REQUIRED = DiagnosticType.warning("JSC_CONSTRUCTOR_REQUIRED", "{0} used without @constructor for {1}"); static final DiagnosticType VAR_ARGS_MUST_BE_LAST = DiagnosticType.warning( "JSC_VAR_ARGS_MUST_BE_LAST", "variable length argument must be last"); static final DiagnosticType OPTIONAL_ARG_AT_END = DiagnosticType.warning( "JSC_OPTIONAL_ARG_AT_END", "optional arguments must be at the end"); static final DiagnosticType INEXISTANT_PARAM = DiagnosticType.warning( "JSC_INEXISTANT_PARAM", "parameter {0} does not appear in {1}''s parameter list"); static final DiagnosticType TYPE_REDEFINITION = DiagnosticType.warning( "JSC_TYPE_REDEFINITION", "attempted re-definition of type {0}\n" + "found : {1}\n" + "expected: {2}"); static final DiagnosticType TEMPLATE_TYPE_DUPLICATED = DiagnosticType.warning( "JSC_TEMPLATE_TYPE_DUPLICATED", "Only one parameter type must be the template type"); static final DiagnosticType TEMPLATE_TYPE_EXPECTED = DiagnosticType.warning( "JSC_TEMPLATE_TYPE_EXPECTED", "The template type must be a parameter type"); static final DiagnosticType THIS_TYPE_NON_OBJECT = DiagnosticType.warning( "JSC_THIS_TYPE_NON_OBJECT", "@this type of a function must be an object\n" + "Actual type: {0}"); static final DiagnosticType SAME_INTERFACE_MULTIPLE_IMPLEMENTS = DiagnosticType.warning( "JSC_SAME_INTERFACE_MULTIPLE_IMPLEMENTS", "Cannot @implement the same interface more than once\n" + "Repeated interface: {0}"); private class ExtendedTypeValidator implements Predicate<JSType> { @Override public boolean apply(JSType type) { ObjectType

Closure, 120

<FILEB>
<CHANGES>
if (ref.getSymbol().getScope()!= ref.scope) {
return false;
}
<CHANGEE>
<FILEE>
<FILEB> return references.get(i); } } return null; } /** * @return Whether the variable is only assigned a value once for its * lifetime. */ boolean isAssignedOnceInLifetime() { Reference ref = getOneAndOnlyAssignment(); if (ref == null) { return false; } // Make sure this assignment is not in a loop. for (BasicBlock block = ref.getBasicBlock(); block != null; block = block.getParent()) { if (block.isFunction) { <CHANGES> <CHANGEE> break; } else if (block.isLoop) { return false; } } return true; } /** * @return The one and only assignment. Returns if there are 0 or 2+ * assignments. */ private Reference getOneAndOnlyAssignment() { <FILEE> <SCANS> objectType = ObjectType.cast(type); if (objectType == null) { reportWarning(EXTENDS_NON_OBJECT, formatFnName(), type.toString()); return false; } else if (objectType.isEmptyType()) { reportWarning(RESOLVED_TAG_EMPTY, "@extends", formatFnName()); return false; } else if (objectType.isUnknownType()) { if (hasMoreTagsToResolve(objectType)) { return true; } else { reportWarning(RESOLVED_TAG_EMPTY, "@extends", fnName); return false; } } else { return true; } } } private class ImplementedTypeValidator implements Predicate<JSType> { @Override public boolean apply(JSType type) { ObjectType objectType = ObjectType.cast(type); if (objectType == null) { reportError(BAD_IMPLEMENTED_TYPE, fnName); return false; } else if (objectType.isEmptyType()) { reportWarning(RESOLVED_TAG_EMPTY, "@implements", fnName); return false; } else if (objectType.isUnknownType()) { if (hasMoreTagsToResolve(objectType)) { return true; } else { reportWarning(RESOLVED_TAG_EMPTY, "@implements", fnName); return false; } } else { return true; } } } /** * @param fnName The function name. * @param compiler The compiler. * @param errorRoot The node to associate with any warning generated by * this builder. * @param sourceName A source name for associating any warnings that * we have to emit. * @param scope The syntactic scope. */ FunctionTypeBuilder(String fnName, AbstractCompiler compiler, Node errorRoot, String sourceName, Scope scope) { Preconditions.checkNotNull(errorRoot); this.fnName = fnName == null ? "" : fnName; this.codingConvention = compiler.getCodingConvention(); this.typeRegistry = compiler.getTypeRegistry(); this.errorRoot = errorRoot; this.sourceName = sourceName; this.compiler = compiler; this.scope = scope; } /** Format the function name for use in warnings. */ String formatFnName() { return fnName.isEmpty() ? "<anonymous>" : fnName; } /** * Sets the contents of this function. */ FunctionTypeBuilder setContents(@Nullable FunctionContents contents) { if (contents != null) { this.contents = contents; } return this; } /** * Infer the parameter and return types of a function from * the parameter and return types of the function it is overriding. * * @param oldType The function being overridden. Does nothing if this is null. * @param paramsParent The LP node of the function that we're assigning to. * If null, that just means we're not initializing this to a function * literal. */ FunctionTypeBuilder inferFromOverriddenFunction( @Nullable FunctionType oldType, @Nullable Node paramsParent) { if (oldType == null) { return this; } returnType = oldType.getReturnType(); returnTypeInferred = oldType.isReturnTypeInferred(); if (paramsParent == null) { // Not a function literal. parametersNode = oldType.getParametersNode(); if (parametersNode == null) { parametersNode = new FunctionParamBuilder(typeRegistry).build(); } } else { // We're overriding with a function literal. Apply type information // to each parameter of the literal. FunctionParamBuilder paramBuilder = new FunctionParamBuilder(typeRegistry);

Closure, 120

<FILEB>
<CHANGES>
if (ref.getSymbol().getScope()!= ref.scope) {
return false;
}
<CHANGEE>
<FILEE>
<FILEB> return references.get(i); } } return null; } /** * @return Whether the variable is only assigned a value once for its * lifetime. */ boolean isAssignedOnceInLifetime() { Reference ref = getOneAndOnlyAssignment(); if (ref == null) { return false; } // Make sure this assignment is not in a loop. for (BasicBlock block = ref.getBasicBlock(); block != null; block = block.getParent()) { if (block.isFunction) { <CHANGES> <CHANGEE> break; } else if (block.isLoop) { return false; } } return true; } /** * @return The one and only assignment. Returns if there are 0 or 2+ * assignments. */ private Reference getOneAndOnlyAssignment() { <FILEE> <SCANS> Iterator<Node> oldParams = oldType.getParameters().iterator(); boolean warnedAboutArgList = false; boolean oldParamsListHitOptArgs = false; for (Node currentParam = paramsParent.getFirstChild(); currentParam != null; currentParam = currentParam.getNext()) { if (oldParams.hasNext()) { Node oldParam = oldParams.next(); Node newParam = paramBuilder.newParameterFromNode(oldParam); oldParamsListHitOptArgs = oldParamsListHitOptArgs || oldParam.isVarArgs() || oldParam.isOptionalArg(); // The subclass method might write its var_args as individual // arguments. if (currentParam.getNext() != null && newParam.isVarArgs()) { newParam.setVarArgs(false); newParam.setOptionalArg(true); } } else { warnedAboutArgList |= addParameter( paramBuilder, typeRegistry.getNativeType(UNKNOWN_TYPE), warnedAboutArgList, codingConvention.isOptionalParameter(currentParam) || oldParamsListHitOptArgs, codingConvention.isVarArgsParameter(currentParam)); } } // Clone any remaining params that aren't in the function literal, // but make them optional. while (oldParams.hasNext()) { paramBuilder.newOptionalParameterFromNode(oldParams.next()); } parametersNode = paramBuilder.build(); } return this; } /** * Infer the return type from JSDocInfo. */ FunctionTypeBuilder inferReturnType(@Nullable JSDocInfo info) { if (info != null && info.hasReturnType()) { returnType = info.getReturnType().evaluate(scope, typeRegistry); returnTypeInferred = false; } return this; } /** * Infer the role of the function (whether it's a constructor or interface) * and what it inherits from in JSDocInfo. */ FunctionTypeBuilder inferInheritance(@Nullable JSDocInfo info) { if (info != null) { isConstructor = info.isConstructor(); makesStructs = info.makesStructs(); makesDicts = info.makesDicts(); isInterface = info.isInterface(); if (makesStructs && !isConstructor) { reportWarning(CONSTRUCTOR_REQUIRED, "@struct", formatFnName()); } else if (makesDicts && !isConstructor) { reportWarning(CONSTRUCTOR_REQUIRED, "@dict", formatFnName()); } // Class template types, which can be used in the scope of a constructor // definition. ImmutableList<String> typeParameters = info.getTemplateTypeNames(); if (!typeParameters.isEmpty()) { if (isConstructor || isInterface) { ImmutableList.Builder<TemplateType> builder = ImmutableList.builder(); for (String typeParameter : typeParameters) { builder.add(typeRegistry.createTemplateType(typeParameter)); } classTemplateTypeNames = builder.build(); typeRegistry.setTemplateTypeNames(classTemplateTypeNames); } } // base type if (info.hasBaseType()) { if (isConstructor) { JSType maybeBaseType = info.getBaseType().evaluate(scope, typeRegistry); if (maybeBaseType != null && maybeBaseType.setValidator(new ExtendedTypeValidator())) { baseType = (ObjectType) maybeBaseType; } } else { reportWarning(EXTENDS_WITHOUT_TYPEDEF, formatFnName()); } } // Implemented interfaces (for constructors only). if (info.getImplementedInterfaceCount() > 0) { if (isConstructor) { implementedInterfaces = Lists.newArrayList(); Set<JSType>

Closure, 120

<FILEB>
<CHANGES>
if (ref.getSymbol().getScope()!= ref.scope) {
return false;
}
<CHANGEE>
<FILEE>
<FILEB> return references.get(i); } } return null; } /** * @return Whether the variable is only assigned a value once for its * lifetime. */ boolean isAssignedOnceInLifetime() { Reference ref = getOneAndOnlyAssignment(); if (ref == null) { return false; } // Make sure this assignment is not in a loop. for (BasicBlock block = ref.getBasicBlock(); block != null; block = block.getParent()) { if (block.isFunction) { <CHANGES> <CHANGEE> break; } else if (block.isLoop) { return false; } } return true; } /** * @return The one and only assignment. Returns if there are 0 or 2+ * assignments. */ private Reference getOneAndOnlyAssignment() { <FILEE> <SCANS> baseInterfaces = new HashSet<JSType>(); for (JSTypeExpression t : info.getImplementedInterfaces()) { JSType maybeInterType = t.evaluate(scope, typeRegistry); if (maybeInterType != null && maybeInterType.setValidator(new ImplementedTypeValidator())) { // Disallow implementing the same base (not templatized) interface // type more than once. JSType baseInterface = maybeInterType; if (baseInterface.toMaybeTemplatizedType() != null) { baseInterface = baseInterface.toMaybeTemplatizedType().getReferencedType(); } if (baseInterfaces.contains(baseInterface)) { reportWarning(SAME_INTERFACE_MULTIPLE_IMPLEMENTS, baseInterface.toString()); } else { baseInterfaces.add(baseInterface); } implementedInterfaces.add((ObjectType) maybeInterType); } } } else if (isInterface) { reportWarning( TypeCheck.CONFLICTING_IMPLEMENTED_TYPE, formatFnName()); } else { reportWarning(CONSTRUCTOR_REQUIRED, "@implements", formatFnName()); } } // extended interfaces (for interfaces only) // We've already emitted a warning if this is not an interface. if (isInterface) { extendedInterfaces = Lists.newArrayList(); for (JSTypeExpression t : info.getExtendedInterfaces()) { JSType maybeInterfaceType = t.evaluate(scope, typeRegistry); if (maybeInterfaceType != null && maybeInterfaceType.setValidator(new ExtendedTypeValidator())) { extendedInterfaces.add((ObjectType) maybeInterfaceType); } } } } return this; } /** * Infers the type of {@code this}. * @param type The type of this if the info is missing. */ FunctionTypeBuilder inferThisType(JSDocInfo info, JSType type) { // Look at the @this annotation first. inferThisType(info); if (thisType == null) { ObjectType objType = ObjectType.cast(type); if (objType != null && (info == null || !info.hasType())) { thisType = objType; } } return this; } /** * Infers the type of {@code this}. * @param info The JSDocInfo for this function. */ FunctionTypeBuilder inferThisType(JSDocInfo info) { JSType maybeThisType = null; if (info != null && info.hasThisType()) { // TODO(johnlenz): In ES5 strict mode a function can have a null or // undefined "this" value, but all the existing "@this" annotations // don't declare restricted types. maybeThisType = info.getThisType().evaluate(scope, typeRegistry) .restrictByNotNullOrUndefined(); } if (maybeThisType != null) { thisType = maybeThisType; } return this; } /** * Infer the parameter types from the doc info alone. */ FunctionTypeBuilder inferParameterTypes(JSDocInfo info) { // Create a fake args parent. Node lp = IR.paramList(); for (String name : info.getParameterNames()) { lp.addChildToBack(IR.name(name)); } return inferParameterTypes(lp, info); } /** * Infer the parameter types from the list of argument names and * the doc info. */ FunctionTypeBuilder inferParameterTypes(@Nullable Node argsParent, @Nullable JSDocInfo info) { if (argsParent == null) { if (info == null) { return this; } else { return inferParameterTypes(info); } }

Closure, 120

<FILEB>
<CHANGES>
if (ref.getSymbol().getScope()!= ref.scope) {
return false;
}
<CHANGEE>
<FILEE>
<FILEB> return references.get(i); } } return null; } /** * @return Whether the variable is only assigned a value once for its * lifetime. */ boolean isAssignedOnceInLifetime() { Reference ref = getOneAndOnlyAssignment(); if (ref == null) { return false; } // Make sure this assignment is not in a loop. for (BasicBlock block = ref.getBasicBlock(); block != null; block = block.getParent()) { if (block.isFunction) { <CHANGES> <CHANGEE> break; } else if (block.isLoop) { return false; } } return true; } /** * @return The one and only assignment. Returns if there are 0 or 2+ * assignments. */ private Reference getOneAndOnlyAssignment() { <FILEE> <SCANS> // arguments Node oldParameterType = null; if (parametersNode != null) { oldParameterType = parametersNode.getFirstChild(); } FunctionParamBuilder builder = new FunctionParamBuilder(typeRegistry); boolean warnedAboutArgList = false; Set<String> allJsDocParams = (info == null) ? Sets.<String>newHashSet() : Sets.newHashSet(info.getParameterNames()); boolean isVarArgs = false; for (Node arg : argsParent.children()) { String argumentName = arg.getString(); allJsDocParams.remove(argumentName); // type from JSDocInfo JSType parameterType = null; boolean isOptionalParam = isOptionalParameter(arg, info); isVarArgs = isVarArgsParameter(arg, info); if (info != null && info.hasParameterType(argumentName)) { parameterType = info.getParameterType(argumentName).evaluate(scope, typeRegistry); } else if (arg.getJSDocInfo() != null && arg.getJSDocInfo().hasType()) { parameterType = arg.getJSDocInfo().getType().evaluate(scope, typeRegistry); } else if (oldParameterType != null && oldParameterType.getJSType() != null) { parameterType = oldParameterType.getJSType(); isOptionalParam = oldParameterType.isOptionalArg(); isVarArgs = oldParameterType.isVarArgs(); } else { parameterType = typeRegistry.getNativeType(UNKNOWN_TYPE); } warnedAboutArgList |= addParameter( builder, parameterType, warnedAboutArgList, isOptionalParam, isVarArgs); if (oldParameterType != null) { oldParameterType = oldParameterType.getNext(); } } // Copy over any old parameters that aren't in the param list. if (!isVarArgs) { while (oldParameterType != null && !isVarArgs) { builder.newParameterFromNode(oldParameterType); oldParameterType = oldParameterType.getNext(); } } for (String inexistentName : allJsDocParams) { reportWarning(INEXISTANT_PARAM, inexistentName, formatFnName()); } parametersNode = builder.build(); return this; } /** * @return Whether the given param is an optional param. */ private boolean isOptionalParameter( Node param, @Nullable JSDocInfo info) { if (codingConvention.isOptionalParameter(param)) { return true; } String paramName = param.getString(); return info != null && info.hasParameterType(paramName) && info.getParameterType(paramName).isOptionalArg(); } /** * Determine whether this is a var args parameter. * @return Whether the given param is a var args param. */ private boolean isVarArgsParameter( Node param, @Nullable JSDocInfo info) { if (codingConvention.isVarArgsParameter(param)) { return true; } String paramName = param.getString(); return info != null && info.hasParameterType(paramName) && info.getParameterType(paramName).isVarArgs(); } /** * Infer the template type from the doc info. */ FunctionTypeBuilder inferTemplateTypeName( @Nullable JSDocInfo info, JSType ownerType) { if (info != null && !info.getTemplateTypeNames().isEmpty()) { ImmutableList.Builder<TemplateType> builder = ImmutableList.builder(); for (String key : info.getTemplateTypeNames()) { builder.add(typeRegistry.createTemplateType(key)); } templateTypeNames = builder.build(); } else { templateTypeNames = ImmutableList.

Closure, 120

<FILEB>
<CHANGES>
if (ref.getSymbol().getScope()!= ref.scope) {
return false;
}
<CHANGEE>
<FILEE>
<FILEB> return references.get(i); } } return null; } /** * @return Whether the variable is only assigned a value once for its * lifetime. */ boolean isAssignedOnceInLifetime() { Reference ref = getOneAndOnlyAssignment(); if (ref == null) { return false; } // Make sure this assignment is not in a loop. for (BasicBlock block = ref.getBasicBlock(); block != null; block = block.getParent()) { if (block.isFunction) { <CHANGES> <CHANGEE> break; } else if (block.isLoop) { return false; } } return true; } /** * @return The one and only assignment. Returns if there are 0 or 2+ * assignments. */ private Reference getOneAndOnlyAssignment() { <FILEE> <SCANS>(JSError.make(sourceName, errorRoot, error, args)); } /** * Determines whether the given JsDoc info declares a function type. */ static boolean isFunctionTypeDeclaration(JSDocInfo info) { return info.getParameterCount() > 0 || info.hasReturnType() || info.hasThisType() || info.isConstructor() || info.isInterface(); } /** * The scope that we should declare this function in, if it needs * to be declared in a scope. Notice that TypedScopeCreator takes * care of most scope-declaring. */ private Scope getScopeDeclaredIn() { int dotIndex = fnName.indexOf("."); if (dotIndex != -1) { String rootVarName = fnName.substring(0, dotIndex); Var rootVar = scope.getVar(rootVarName); if (rootVar != null) { return rootVar.getScope(); } } return scope; } /** * Check whether a type is resolvable in the future * If this has a supertype that hasn't been resolved yet, then we can assume * this type will be OK once the super type resolves. * @param objectType * @return true if objectType is resolvable in the future */ private static boolean hasMoreTagsToResolve(ObjectType objectType) { Preconditions.checkArgument(objectType.isUnknownType()); if (objectType.getImplicitPrototype() != null) { // constructor extends class if (objectType.getImplicitPrototype().isResolved()) { return false; } else { return true; } } else { // interface extends interfaces FunctionType ctor = objectType.getConstructor(); if (ctor != null) { for (ObjectType interfaceType : ctor.getExtendedInterfaces()) { if (!interfaceType.isResolved()) { return true; } } } return false; } } /** Holds data dynamically inferred about functions. */ static interface FunctionContents { /** Returns the source node of this function. May be null. */ Node getSourceNode(); /** Returns if the function may be in externs. */ boolean mayBeFromExterns(); /** Returns if a return of a real value (not undefined) appears. */ boolean mayHaveNonEmptyReturns(); /** Returns if this consists of a single throw. */ boolean mayHaveSingleThrow(); /** Gets a list of variables in this scope that are escaped. */ Iterable<String> getEscapedVarNames(); /** Gets a list of variables whose properties are escaped. */ Set<String> getEscapedQualifiedNames(); /** Gets the number of times each variable has been assigned. */ Multiset<String> getAssignedNameCounts(); } static class UnknownFunctionContents implements FunctionContents { private static UnknownFunctionContents singleton = new UnknownFunctionContents(); static FunctionContents get() { return singleton; } @Override public Node getSourceNode() { return null; } @Override public boolean mayBeFromExterns() { return true; } @Override public boolean mayHaveNonEmptyReturns() { return true; } @Override public boolean mayHaveSingleThrow() { return true; } @Override public Iterable<String> getEscapedVarNames() { return ImmutableList.of(); } @Override public Set<String> getEscapedQualifiedNames() { return ImmutableSet.of(); } @Override public Multiset<String> getAssignedNameCounts() { return ImmutableMultiset.of(); } } static class AstFunctionContents implements FunctionContents { private final Node n; private boolean hasNonEmptyReturns =

Closure, 120

<FILEB>
<CHANGES>
if (ref.getSymbol().getScope()!= ref.scope) {
return false;
}
<CHANGEE>
<FILEE>
<FILEB> return references.get(i); } } return null; } /** * @return Whether the variable is only assigned a value once for its * lifetime. */ boolean isAssignedOnceInLifetime() { Reference ref = getOneAndOnlyAssignment(); if (ref == null) { return false; } // Make sure this assignment is not in a loop. for (BasicBlock block = ref.getBasicBlock(); block != null; block = block.getParent()) { if (block.isFunction) { <CHANGES> <CHANGEE> break; } else if (block.isLoop) { return false; } } return true; } /** * @return The one and only assignment. Returns if there are 0 or 2+ * assignments. */ private Reference getOneAndOnlyAssignment() { <FILEE> <SCANS> } } private void findPrototypeProps(String type, Set<String> props) { Name slot = namespace.getSlot(type); if (slot != null) { for (Ref ref : slot.getRefs()) { if (ref.type == Ref.Type.PROTOTYPE_GET) { Node fullName = ref.getNode().getParent().getParent(); if (fullName.isGetProp()) { props.add(fullName.getLastChild().getString()); } } } } } /** * Checks to make sure all the descendants of a name are defined if they * are referenced. * * @param name A global name. * @param nameIsDefined If true, {@code name} is defined. Otherwise, it's * undefined, and any references to descendant names should emit warnings. */ private void checkDescendantNames(Name name, boolean nameIsDefined) { if (name.props != null) { for (Name prop : name.props) { // if the ancestor of a property is not defined, then we should emit // warnings for all references to the property. boolean propIsDefined = false; if (nameIsDefined) { // if the ancestor of a property is defined, then let's check that // the property is also explicitly defined if it needs to be. propIsDefined = (!propertyMustBeInitializedByFullName(prop) || prop.globalSets + prop.localSets > 0); } validateName(prop, propIsDefined); checkDescendantNames(prop, propIsDefined); } } } private void validateName(Name name, boolean isDefined) { // If the name is not defined, emit warnings for each reference. While // we're looking through each reference, check all the module dependencies. Ref declaration = name.getDeclaration(); Name parent = name.parent; JSModuleGraph moduleGraph = compiler.getModuleGraph(); for (Ref ref : name.getRefs()) { // Don't worry about global exprs. boolean isGlobalExpr = ref.getNode().getParent().isExprResult(); if (!isDefined && !isTypedef(ref)) { if (!isGlobalExpr) { reportRefToUndefinedName(name, ref); } } else if (declaration != null && ref.getModule() != declaration.getModule() && !moduleGraph.dependsOn( ref.getModule(), declaration.getModule())) { reportBadModuleReference(name, ref); } else { // Check for late references. if (ref.scope.isGlobal()) { // Prototype references are special, because in our reference graph, // A.prototype counts as a reference to A. boolean isPrototypeGet = (ref.type == Ref.Type.PROTOTYPE_GET); Name owner = isPrototypeGet ? name : parent; boolean singleGlobalParentDecl = owner != null && owner.getDeclaration() != null && owner.localSets == 0; if (singleGlobalParentDecl && owner.getDeclaration().preOrderIndex > ref.preOrderIndex) { String refName = isPrototypeGet ? name.getFullName() + ".prototype" : name.getFullName(); compiler.report( JSError.make(ref.source.getName(), ref.node, NAME_DEFINED_LATE_WARNING, refName, owner.getFullName(), owner.getDeclaration().source.getName(), String.valueOf(owner.getDeclaration().node.getLineno()))); } } } } } private boolean isTypedef(Ref ref) { // If this is an annotated EXPR-GET, don't do anything. Node parent = ref.node.getParent();

Closure, 120

<FILEB>
<CHANGES>
if (ref.getSymbol().getScope()!= ref.scope) {
return false;
}
<CHANGEE>
<FILEE>
<FILEB> return references.get(i); } } return null; } /** * @return Whether the variable is only assigned a value once for its * lifetime. */ boolean isAssignedOnceInLifetime() { Reference ref = getOneAndOnlyAssignment(); if (ref == null) { return false; } // Make sure this assignment is not in a loop. for (BasicBlock block = ref.getBasicBlock(); block != null; block = block.getParent()) { if (block.isFunction) { <CHANGES> <CHANGEE> break; } else if (block.isLoop) { return false; } } return true; } /** * @return The one and only assignment. Returns if there are 0 or 2+ * assignments. */ private Reference getOneAndOnlyAssignment() { <FILEE> <SCANS>getReferenceName(), type); } /** * Returns a collection of types that directly implement {@code * interfaceInstance}. Subtypes of implementing types are not guaranteed to * be returned. {@code interfaceInstance} must be an ObjectType for the * instance of the interface. */ public Collection<FunctionType> getDirectImplementors( ObjectType interfaceInstance) { return interfaceToImplementors.get(interfaceInstance.getReferenceName()); } /** * Records declared global type names. This makes resolution faster * and more robust in the common case. * * @param name The name of the type to be recorded. * @param t The actual type being associated with the name. * @return True if this name is not already defined, false otherwise. */ public boolean declareType(String name, JSType t) { if (namesToTypes.containsKey(name)) { return false; } register(t, name); return true; } /** * Overrides a declared global type name. Throws an exception if this * type name hasn't been declared yet. */ public void overwriteDeclaredType(String name, JSType t) { Preconditions.checkState(namesToTypes.containsKey(name)); register(t, name); } /** * Records a forward-declared type name. We will not emit errors if this * type name never resolves to anything. */ public void forwardDeclareType(String name) { forwardDeclaredTypes.add(name); } /** * Whether this is a forward-declared type name. */ public boolean isForwardDeclaredType(String name) { return forwardDeclaredTypes.contains(name); } /** Determines whether the given JS package exists. */ public boolean hasNamespace(String name) { return namespaces.contains(name); } /** * Looks up a type by name. * * @param jsTypeName The name string. * @return the corresponding JSType object or {@code null} it cannot be found */ public JSType getType(String jsTypeName) { // TODO(user): Push every local type name out of namesToTypes so that // NamedType#resolve is correct. TemplateType templateType = templateTypes.get(jsTypeName); if (templateType != null) { return templateType; } return namesToTypes.get(jsTypeName); } public JSType getNativeType(JSTypeNative typeId) { return nativeTypes[typeId.ordinal()]; } public ObjectType getNativeObjectType(JSTypeNative typeId) { return (ObjectType) getNativeType(typeId); } public FunctionType getNativeFunctionType(JSTypeNative typeId) { return (FunctionType) getNativeType(typeId); } /** * Looks up a type by name. To allow for forward references to types, an * unrecognized string has to be bound to a NamedType object that will be * resolved later. * * @param scope A scope for doing type name resolution. * @param jsTypeName The name string. * @param sourceName The name of the source file where this reference appears. * @param lineno The line number of the reference. * @return a NamedType if the string argument is not one of the known types, * otherwise the corresponding JSType object. */ public JSType getType(StaticScope<JSType> scope, String jsTypeName, String sourceName, int lineno, int charno) { // Resolve template type names JSType type = null; JSType thisType = null; if (scope != null && scope.getTypeOfThis() != null) { thisType = scope.getTypeOfThis().toObjectType(); } if (thisType != null

Closure, 120

<FILEB>
<CHANGES>
if (ref.getSymbol().getScope()!= ref.scope) {
return false;
}
<CHANGEE>
<FILEE>
<FILEB> return references.get(i); } } return null; } /** * @return Whether the variable is only assigned a value once for its * lifetime. */ boolean isAssignedOnceInLifetime() { Reference ref = getOneAndOnlyAssignment(); if (ref == null) { return false; } // Make sure this assignment is not in a loop. for (BasicBlock block = ref.getBasicBlock(); block != null; block = block.getParent()) { if (block.isFunction) { <CHANGES> <CHANGEE> break; } else if (block.isLoop) { return false; } } return true; } /** * @return The one and only assignment. Returns if there are 0 or 2+ * assignments. */ private Reference getOneAndOnlyAssignment() { <FILEE> <SCANS>) { type = thisType.getTemplateTypeMap().getTemplateTypeKeyByName(jsTypeName); if (type != null) { Preconditions.checkState(type.isTemplateType(), "expected:" + type); return type; } } type = getType(jsTypeName); if (type == null) { // TODO(user): Each instance should support named type creation using // interning. NamedType namedType = new NamedType(this, jsTypeName, sourceName, lineno, charno); unresolvedNamedTypes.put(scope, namedType); type = namedType; } return type; } /** * Flushes out the current resolved and unresolved Named Types from * the type registry. This is intended to be used ONLY before a * compile is run. */ public void clearNamedTypes() { resolvedNamedTypes.clear(); unresolvedNamedTypes.clear(); } /** * Resolve all the unresolved types in the given scope. */ public void resolveTypesInScope(StaticScope<JSType> scope) { for (NamedType type : unresolvedNamedTypes.get(scope)) { type.resolve(reporter, scope); } resolvedNamedTypes.putAll(scope, unresolvedNamedTypes.removeAll(scope)); if (scope != null && scope.getParentScope() == null) { // By default, the global "this" type is just an anonymous object. // If the user has defined a Window type, make the Window the // implicit prototype of "this". PrototypeObjectType globalThis = (PrototypeObjectType) getNativeType( JSTypeNative.GLOBAL_THIS); JSType windowType = getType("Window"); if (globalThis.isUnknownType()) { ObjectType windowObjType = ObjectType.cast(windowType); if (windowObjType != null) { globalThis.setImplicitPrototype(windowObjType); } else { globalThis.setImplicitPrototype( getNativeObjectType(JSTypeNative.OBJECT_TYPE)); } } } } /** * Creates a type representing optional values of the given type. * @return the union of the type and the void type */ public JSType createOptionalType(JSType type) { if (type instanceof UnknownType || type.isAllType()) { return type; } else { return createUnionType(type, getNativeType(JSTypeNative.VOID_TYPE)); } } /** * Creates a type representing nullable values of the given type. * @return the union of the type and the Null type */ public JSType createDefaultObjectUnion(JSType type) { if (type.isTemplateType()) { // Template types represent the substituted type exactly and should // not be wrapped. return type; } else { return shouldTolerateUndefinedValues() ? createOptionalNullableType(type) : createNullableType(type); } } /** * Creates a type representing nullable values of the given type. * @return the union of the type and the Null type */ public JSType createNullableType(JSType type) { return createUnionType(type, getNativeType(JSTypeNative.NULL_TYPE)); } /** * Creates a nullable and undefine-able value of the given type. * @return The union of the type and null and undefined. */ public JSType createOptionalNullableType(JSType type) { return createUnionType(type, getNativeType(JSTypeNative.VOID_TYPE), getNativeType(JSTypeNative.NULL_TYPE)); } /** * Creates a union type whose variants are the arguments. */ public JSType createUnionType(JSType...

Closure, 120

<FILEB>
<CHANGES>
if (ref.getSymbol().getScope()!= ref.scope) {
return false;
}
<CHANGEE>
<FILEE>
<FILEB> return references.get(i); } } return null; } /** * @return Whether the variable is only assigned a value once for its * lifetime. */ boolean isAssignedOnceInLifetime() { Reference ref = getOneAndOnlyAssignment(); if (ref == null) { return false; } // Make sure this assignment is not in a loop. for (BasicBlock block = ref.getBasicBlock(); block != null; block = block.getParent()) { if (block.isFunction) { <CHANGES> <CHANGEE> break; } else if (block.isLoop) { return false; } } return true; } /** * @return The one and only assignment. Returns if there are 0 or 2+ * assignments. */ private Reference getOneAndOnlyAssignment() { <FILEE> <SCANS> representing a type. * @param n The node with type info. * @param sourceName The source file name. * @param scope A scope for doing type name lookups. */ public JSType createFromTypeNodes(Node n, String sourceName, StaticScope<JSType> scope) { if (resolveMode == ResolveMode.LAZY_EXPRESSIONS) { // If the type expression doesn't contain any names, just // resolve it anyway. boolean hasNames = hasTypeName(n); if (hasNames) { return new UnresolvedTypeExpression(this, n, sourceName); } } return createFromTypeNodesInternal(n, sourceName, scope); } private boolean hasTypeName(Node n) { if (n.getType() == Token.STRING) { return true; } for (Node child = n.getFirstChild(); child != null; child = child.getNext()) { if (hasTypeName(child)) { return true; } } return false; } /** @see #createFromTypeNodes(Node, String, StaticScope) */ private JSType createFromTypeNodesInternal(Node n, String sourceName, StaticScope<JSType> scope) { switch (n.getType()) { case Token.LC: // Record type. return createRecordTypeFromNodes( n.getFirstChild(), sourceName, scope); case Token.BANG: // Not nullable return createFromTypeNodesInternal( n.getFirstChild(), sourceName, scope) .restrictByNotNullOrUndefined(); case Token.QMARK: // Nullable or unknown Node firstChild = n.getFirstChild(); if (firstChild == null) { return getNativeType(UNKNOWN_TYPE); } return createDefaultObjectUnion( createFromTypeNodesInternal( firstChild, sourceName, scope)); case Token.EQUALS: // Optional return createOptionalType( createFromTypeNodesInternal( n.getFirstChild(), sourceName, scope)); case Token.ELLIPSIS: // Var args return createOptionalType( createFromTypeNodesInternal( n.getFirstChild(), sourceName, scope)); case Token.STAR: // The AllType return getNativeType(ALL_TYPE); case Token.LB: // Array type // TODO(nicksantos): Enforce membership restrictions on the Array. return getNativeType(ARRAY_TYPE); case Token.PIPE: // Union type UnionTypeBuilder builder = new UnionTypeBuilder(this); for (Node child = n.getFirstChild(); child != null; child = child.getNext()) { builder.addAlternate( createFromTypeNodesInternal(child, sourceName, scope)); } return builder.build(); case Token.EMPTY: // When the return value of a function is not specified return getNativeType(UNKNOWN_TYPE); case Token.VOID: // Only allowed in the return value of a function. return getNativeType(VOID_TYPE); case Token.STRING: JSType namedType = getType(scope, n.getString(), sourceName, n.getLineno(), n.getCharno()); if (resolveMode != ResolveMode.LAZY_NAMES) { namedType = namedType.resolveInternal(reporter, scope); } if ((namedType instanceof ObjectType) && !(nonNullableTypeNames.contains(n.getString()))) { Node typeList = n.getFirstChild(); int nAllowedTypes = namedType.getTemplateTypeMap().numUnfilledTemplateKeys(); if (typeList != null && nAllowedTypes > 0) { // Templatized types. ImmutableList.Builder<JSType> templateTypes = ImmutableList.builder(); // Special case

Closure, 120

<FILEB>
<CHANGES>
if (ref.getSymbol().getScope()!= ref.scope) {
return false;
}
<CHANGEE>
<FILEE>
<FILEB> return references.get(i); } } return null; } /** * @return Whether the variable is only assigned a value once for its * lifetime. */ boolean isAssignedOnceInLifetime() { Reference ref = getOneAndOnlyAssignment(); if (ref == null) { return false; } // Make sure this assignment is not in a loop. for (BasicBlock block = ref.getBasicBlock(); block != null; block = block.getParent()) { if (block.isFunction) { <CHANGES> <CHANGEE> break; } else if (block.isLoop) { return false; } } return true; } /** * @return The one and only assignment. Returns if there are 0 or 2+ * assignments. */ private Reference getOneAndOnlyAssignment() { <FILEE> <SCANS> for Object, where Object.<X> implies Object.<?,X>. if (n.getString().equals("Object") && typeList.getFirstChild() == typeList.getLastChild()) { templateTypes.add(getNativeType(UNKNOWN_TYPE)); } int templateNodeIndex = 0; for (Node templateNode : typeList.getFirstChild().siblings()) { // Don't parse more templatized type nodes than the type can // accommodate. This is because some existing clients have // template annotations on non-templatized classes, for instance: // goog.structs.Set.<SomeType> // The problem in these cases is that the previously-unparsed // SomeType is not actually a valid type. To prevent these clients // from seeing unknown type errors, we explicitly don't parse // these types. // TODO(user): Address this issue by removing bad template // annotations on non-templatized classes. if (++templateNodeIndex > nAllowedTypes) { break; } templateTypes.add(createFromTypeNodesInternal( templateNode, sourceName, scope)); } namedType = createTemplatizedType( (ObjectType) namedType, templateTypes.build()); Preconditions.checkNotNull(namedType); } return createDefaultObjectUnion(namedType); } else { return namedType; } case Token.FUNCTION: ObjectType thisType = null; boolean isConstructor = false; Node current = n.getFirstChild(); if (current.getType() == Token.THIS || current.getType() == Token.NEW) { Node contextNode = current.getFirstChild(); thisType = ObjectType.cast( createFromTypeNodesInternal( contextNode, sourceName, scope) .restrictByNotNullOrUndefined()); if (thisType == null) { reporter.warning( SimpleErrorReporter.getMessage0( current.getType() == Token.THIS ? "msg.jsdoc.function.thisnotobject" : "msg.jsdoc.function.newnotobject"), sourceName, contextNode.getLineno(), contextNode.getCharno()); } isConstructor = current.getType() == Token.NEW; current = current.getNext(); } FunctionParamBuilder paramBuilder = new FunctionParamBuilder(this); if (current.getType() == Token.PARAM_LIST) { for (Node arg = current.getFirstChild(); arg != null; arg = arg.getNext()) { if (arg.getType() == Token.ELLIPSIS) { if (arg.getChildCount() == 0) { paramBuilder.addVarArgs(getNativeType(UNKNOWN_TYPE)); } else { paramBuilder.addVarArgs( createFromTypeNodesInternal( arg.getFirstChild(), sourceName, scope)); } } else { JSType type = createFromTypeNodesInternal( arg, sourceName, scope); if (arg.getType() == Token.EQUALS) { boolean addSuccess = paramBuilder.addOptionalParams(type); if (!addSuccess) { reporter.warning( SimpleErrorReporter.getMessage0( "msg.jsdoc.function.varargs"), sourceName, arg.getLineno(), arg.getCharno()); } } else { paramBuilder.addRequiredParams(type); } } } current = current.getNext(); } JSType returnType = createFromTypeNodesInternal(current, sourceName, scope); return new FunctionBuilder(this) .withParams(paramBuilder) .withReturnType(returnType) .withTypeOfThis(thisType) .setIsConstructor(isConstructor) .build(); } throw new IllegalStateException( "

Closure, 120

<FILEB>
<CHANGES>
if (ref.getSymbol().getScope()!= ref.scope) {
return false;
}
<CHANGEE>
<FILEE>
<FILEB> return references.get(i); } } return null; } /** * @return Whether the variable is only assigned a value once for its * lifetime. */ boolean isAssignedOnceInLifetime() { Reference ref = getOneAndOnlyAssignment(); if (ref == null) { return false; } // Make sure this assignment is not in a loop. for (BasicBlock block = ref.getBasicBlock(); block != null; block = block.getParent()) { if (block.isFunction) { <CHANGES> <CHANGEE> break; } else if (block.isLoop) { return false; } } return true; } /** * @return The one and only assignment. Returns if there are 0 or 2+ * assignments. */ private Reference getOneAndOnlyAssignment() { <FILEE> <SCANS>Unexpected node in type expression: " + n.toString()); } /** * Creates a RecordType from the nodes representing said record type. * @param n The node with type info. * @param sourceName The source file name. * @param scope A scope for doing type name lookups. */ private JSType createRecordTypeFromNodes(Node n, String sourceName, StaticScope<JSType> scope) { RecordTypeBuilder builder = new RecordTypeBuilder(this); // For each of the fields in the record type. for (Node fieldTypeNode = n.getFirstChild(); fieldTypeNode != null; fieldTypeNode = fieldTypeNode.getNext()) { // Get the property's name. Node fieldNameNode = fieldTypeNode; boolean hasType = false; if (fieldTypeNode.getType() == Token.COLON) { fieldNameNode = fieldTypeNode.getFirstChild(); hasType = true; } String fieldName = fieldNameNode.getString(); // TODO(user): Move this into the lexer/parser. // Remove the string literal characters around a field name, // if any. if (fieldName.startsWith("'") || fieldName.startsWith("\"")) { fieldName = fieldName.substring(1, fieldName.length() - 1); } // Get the property's type. JSType fieldType = null; if (hasType) { // We have a declared type. fieldType = createFromTypeNodesInternal( fieldTypeNode.getLastChild(), sourceName, scope); } else { // Otherwise, the type is UNKNOWN. fieldType = getNativeType(JSTypeNative.UNKNOWN_TYPE); } // Add the property to the record. if (builder.addProperty(fieldName, fieldType, fieldNameNode) == null) { // Duplicate field name, warning and skip reporter.warning( "Duplicate record field " + fieldName, sourceName, n.getLineno(), fieldNameNode.getCharno()); } } return builder.build(); } /** * Sets the template type name. */ public void setTemplateTypeNames(List<TemplateType> keys) { Preconditions.checkNotNull(keys); for (TemplateType key : keys) { templateTypes.put(key.getReferenceName(), key); } } /** * Clears the template type name. */ public void clearTemplateTypeNames() { templateTypes.clear(); } }

Closure, 120

<FILEB>
<CHANGES>
if (ref.getSymbol().getScope()!= ref.scope) {
return false;
}
<CHANGEE>
<FILEE>
<FILEB> return references.get(i); } } return null; } /** * @return Whether the variable is only assigned a value once for its * lifetime. */ boolean isAssignedOnceInLifetime() { Reference ref = getOneAndOnlyAssignment(); if (ref == null) { return false; } // Make sure this assignment is not in a loop. for (BasicBlock block = ref.getBasicBlock(); block != null; block = block.getParent()) { if (block.isFunction) { <CHANGES> <CHANGEE> break; } else if (block.isLoop) { return false; } } return true; } /** * @return The one and only assignment. Returns if there are 0 or 2+ * assignments. */ private Reference getOneAndOnlyAssignment() { <FILEE> <SCANS> */ static boolean isSubtypeHelper(JSType thisType, JSType thatType) { // unknown if (thatType.isUnknownType()) { return true; } // all type if (thatType.isAllType()) { return true; } // equality if (thisType.isEquivalentTo(thatType)) { return true; } // unions if (thatType.isUnionType()) { UnionType union = thatType.toMaybeUnionType(); for (JSType element : union.alternates) { if (thisType.isSubtype(element)) { return true; } } return false; } // TemplateTypeMaps. This check only returns false if the TemplateTypeMaps // are not equivalent. TemplateTypeMap thisTypeParams = thisType.getTemplateTypeMap(); TemplateTypeMap thatTypeParams = thatType.getTemplateTypeMap(); boolean templateMatch = true; if (isExemptFromTemplateTypeInvariance(thatType)) { // Array and Object are exempt from template type invariance; their // template types maps are considered a match only if the ObjectElementKey // values are subtypes/supertypes of one another. TemplateType key = thisType.registry.getObjectElementKey(); JSType thisElement = thisTypeParams.getTemplateType(key); JSType thatElement = thatTypeParams.getTemplateType(key); templateMatch = thisElement.isSubtype(thatElement) || thatElement.isSubtype(thisElement); } else { templateMatch = thisTypeParams.checkEquivalenceHelper( thatTypeParams, EquivalenceMethod.INVARIANT); } if (!templateMatch) { return false; } // Templatized types. The above check guarantees TemplateTypeMap // equivalence; check if the base type is a subtype. if (thisType.isTemplatizedType()) { return thisType.toMaybeTemplatizedType().getReferencedType().isSubtype( thatType); } // proxy types if (thatType instanceof ProxyObjectType) { return thisType.isSubtype( ((ProxyObjectType) thatType).getReferencedTypeInternal()); } return false; } /** * Determines if the specified type is exempt from standard invariant * templatized typing rules. */ static boolean isExemptFromTemplateTypeInvariance(JSType type) { ObjectType objType = type.toObjectType(); return objType == null || "Array".equals(objType.getReferenceName()) || "Object".equals(objType.getReferenceName()); } /** * Visit this type with the given visitor. * @see com.google.javascript.rhino.jstype.Visitor * @return the value returned by the visitor */ public abstract <T> T visit(Visitor<T> visitor); /** * Visit the types with the given visitor. * @see com.google.javascript.rhino.jstype.RelationshipVisitor * @return the value returned by the visitor */ abstract <T> T visit(RelationshipVisitor<T> visitor, JSType that); /** * Force this type to resolve, even if the registry is in a lazy * resolving mode. * @see #resolve */ public final JSType forceResolve(ErrorReporter t, StaticScope<JSType> scope) { ResolveMode oldResolveMode = registry.getResolveMode(); registry.setResolveMode(ResolveMode.IMMEDIATE); JSType result = resolve(t, scope); registry.setResolveMode(oldResolveMode); return result; } /** * Resolve this type in the given scope. * * The returned value must be equal to {@code this}, as defined

Closure, 120

<FILEB>
<CHANGES>
if (ref.getSymbol().getScope()!= ref.scope) {
return false;
}
<CHANGEE>
<FILEE>
<FILEB> return references.get(i); } } return null; } /** * @return Whether the variable is only assigned a value once for its * lifetime. */ boolean isAssignedOnceInLifetime() { Reference ref = getOneAndOnlyAssignment(); if (ref == null) { return false; } // Make sure this assignment is not in a loop. for (BasicBlock block = ref.getBasicBlock(); block != null; block = block.getParent()) { if (block.isFunction) { <CHANGES> <CHANGEE> break; } else if (block.isLoop) { return false; } } return true; } /** * @return The one and only assignment. Returns if there are 0 or 2+ * assignments. */ private Reference getOneAndOnlyAssignment() { <FILEE> <SCANS> by * {@link #isEquivalentTo}. It may or may not be the same object. This method * may modify the internal state of {@code this}, as long as it does * so in a way that preserves Object equality. * * For efficiency, we should only resolve a type once per compilation job. * For incremental compilations, one compilation job may need the * artifacts from a previous generation, so we will eventually need * a generational flag instead of a boolean one. */ public final JSType resolve(ErrorReporter t, StaticScope<JSType> scope) { if (resolved) { // TODO(nicksantos): Check to see if resolve() looped back on itself. // Preconditions.checkNotNull(resolveResult); if (resolveResult == null) { return registry.getNativeType(JSTypeNative.UNKNOWN_TYPE); } return resolveResult; } resolved = true; resolveResult = resolveInternal(t, scope); resolveResult.setResolvedTypeInternal(resolveResult); return resolveResult; } /** * @see #resolve */ abstract JSType resolveInternal(ErrorReporter t, StaticScope<JSType> scope); void setResolvedTypeInternal(JSType type) { resolveResult = type; resolved = true; } /** Whether the type has been resolved. */ public final boolean isResolved() { return resolved; } /** Clears the resolved field. */ public final void clearResolved() { resolved = false; resolveResult = null; } /** * A null-safe resolve. * @see #resolve */ static final JSType safeResolve( JSType type, ErrorReporter t, StaticScope<JSType> scope) { return type == null ? null : type.resolve(t, scope); } /** * Certain types have constraints on them at resolution-time. * For example, a type in an {@code @extends} annotation must be an * object. Clients should inject a validator that emits a warning * if the type does not validate, and return false. */ public boolean setValidator(Predicate<JSType> validator) { return validator.apply(this); } public static class TypePair { public final JSType typeA; public final JSType typeB; public TypePair(JSType typeA, JSType typeB) { this.typeA = typeA; this.typeB = typeB; } } /** * A string representation of this type, suitable for printing * in warnings. */ @Override public String toString() { return toStringHelper(false); } /** * A hash code function for diagnosing complicated issues * around type-identity. */ public String toDebugHashCodeString() { return "{" + hashCode() + "}"; } /** * A string representation of this type, suitable for printing * in type annotations at code generation time. */ public final String toAnnotationString() { return toStringHelper(true); } /** * @param forAnnotations Whether this is for use in code generator * annotations. Otherwise, it's for warnings. */ abstract String toStringHelper(boolean forAnnotations); /** * Modify this type so that it matches the specified type. * * This is useful for reverse type-inference, where we want to * infer that an object literal matches its constraint (much like * how the java compiler does reverse-inference to figure out generics). * @param constraint */ public void matchConstraint(JSType constraint) {} }

Closure, 120

<FILEB>
<CHANGES>
if (ref.getSymbol().getScope()!= ref.scope) {
return false;
}
<CHANGEE>
<FILEE>
<FILEB> return references.get(i); } } return null; } /** * @return Whether the variable is only assigned a value once for its * lifetime. */ boolean isAssignedOnceInLifetime() { Reference ref = getOneAndOnlyAssignment(); if (ref == null) { return false; } // Make sure this assignment is not in a loop. for (BasicBlock block = ref.getBasicBlock(); block != null; block = block.getParent()) { if (block.isFunction) { <CHANGES> <CHANGEE> break; } else if (block.isLoop) { return false; } } return true; } /** * @return The one and only assignment. Returns if there are 0 or 2+ * assignments. */ private Reference getOneAndOnlyAssignment() { <FILEE> <SCANS> lists give a type for this param, it should be equal if (otherParamType != null && !thisParamType.checkEquivalenceHelper( otherParamType, eqMethod)) { return false; } } else { if (otherParamType != null) { return false; } } // Check var_args/optionality if (thisParam.isOptionalArg() != otherParam.isOptionalArg()) { return false; } if (thisParam.isVarArgs() != otherParam.isVarArgs()) { return false; } thisParam = thisParam.getNext(); otherParam = otherParam.getNext(); } // One of the parameters is null, so the types are only equal if both // parameter lists are null (they are equal). return thisParam == otherParam; } boolean checkArrowEquivalenceHelper( ArrowType that, EquivalenceMethod eqMethod) { // Please keep this method in sync with the hashCode() method below. if (!returnType.checkEquivalenceHelper(that.returnType, eqMethod)) { return false; } return hasEqualParameters(that, eqMethod); } @Override public int hashCode() { int hashCode = 0; if (returnType != null) { hashCode += returnType.hashCode(); } if (returnTypeInferred) { hashCode += 1; } if (parameters != null) { Node param = parameters.getFirstChild(); while (param != null) { JSType paramType = param.getJSType(); if (paramType != null) { hashCode += paramType.hashCode(); } param = param.getNext(); } } return hashCode; } @Override public JSType getLeastSupertype(JSType that) { throw new UnsupportedOperationException(); } @Override public JSType getGreatestSubtype(JSType that) { throw new UnsupportedOperationException(); } @Override public TernaryValue testForEquality(JSType that) { throw new UnsupportedOperationException(); } @Override public <T> T visit(Visitor<T> visitor) { throw new UnsupportedOperationException(); } @Override <T> T visit(RelationshipVisitor<T> visitor, JSType that) { throw new UnsupportedOperationException(); } @Override public BooleanLiteralSet getPossibleToBooleanOutcomes() { return BooleanLiteralSet.TRUE; } @Override JSType resolveInternal(ErrorReporter t, StaticScope<JSType> scope) { returnType = safeResolve(returnType, t, scope); if (parameters != null) { for (Node paramNode = parameters.getFirstChild(); paramNode != null; paramNode = paramNode.getNext()) { paramNode.setJSType(paramNode.getJSType().resolve(t, scope)); } } return this; } boolean hasUnknownParamsOrReturn() { if (parameters != null) { for (Node paramNode = parameters.getFirstChild(); paramNode != null; paramNode = paramNode.getNext()) { JSType type = paramNode.getJSType(); if (type == null || type.isUnknownType()) { return true; } } } return returnType == null || returnType.isUnknownType(); } @Override String toStringHelper(boolean forAnnotations) { return "[ArrowType]"; } @Override public boolean hasAnyTemplateTypesInternal() { return returnType.hasAnyTemplateTypes() || hasTemplatedParameterType(); } private boolean hasTemplatedParameterType() { if (parameters != null) { for (Node paramNode = parameters.getFirstChild(); paramNode != null; paramNode = paramNode.getNext()) { JSType type = paramNode.get

Closure, 120

<FILEB>
<CHANGES>
if (ref.getSymbol().getScope()!= ref.scope) {
return false;
}
<CHANGEE>
<FILEE>
<FILEB> return references.get(i); } } return null; } /** * @return Whether the variable is only assigned a value once for its * lifetime. */ boolean isAssignedOnceInLifetime() { Reference ref = getOneAndOnlyAssignment(); if (ref == null) { return false; } // Make sure this assignment is not in a loop. for (BasicBlock block = ref.getBasicBlock(); block != null; block = block.getParent()) { if (block.isFunction) { <CHANGES> <CHANGEE> break; } else if (block.isLoop) { return false; } } return true; } /** * @return The one and only assignment. Returns if there are 0 or 2+ * assignments. */ private Reference getOneAndOnlyAssignment() { <FILEE> <SCANS>JSC_CTOR_INITIALIZER_NOT_CTOR", "Constructor {0} must be initialized at declaration"); static final DiagnosticType IFACE_INITIALIZER = DiagnosticType.warning( "JSC_IFACE_INITIALIZER_NOT_IFACE", "Interface {0} must be initialized at declaration"); static final DiagnosticType CONSTRUCTOR_EXPECTED = DiagnosticType.warning( "JSC_REFLECT_CONSTRUCTOR_EXPECTED", "Constructor expected as first argument"); static final DiagnosticType UNKNOWN_LENDS = DiagnosticType.warning( "JSC_UNKNOWN_LENDS", "Variable {0} not declared before @lends annotation."); static final DiagnosticType LENDS_ON_NON_OBJECT = DiagnosticType.warning( "JSC_LENDS_ON_NON_OBJECT", "May only lend properties to object types. {0} has type {1}."); private final AbstractCompiler compiler; private final ErrorReporter typeParsingErrorReporter; private final TypeValidator validator; private final CodingConvention codingConvention; private final JSTypeRegistry typeRegistry; private final List<ObjectType> delegateProxyPrototypes = Lists.newArrayList(); private final Map<String, String> delegateCallingConventions = Maps.newHashMap(); // Simple properties inferred about functions. private final Map<Node, AstFunctionContents> functionAnalysisResults = Maps.newHashMap(); // For convenience private final ObjectType unknownType; /** * Defer attachment of types to nodes until all type names * have been resolved. Then, we can resolve the type and attach it. */ private class DeferredSetType { final Node node; final JSType type; DeferredSetType(Node node, JSType type) { Preconditions.checkNotNull(node); Preconditions.checkNotNull(type); this.node = node; this.type = type; // Other parts of this pass may read off the node. // (like when we set the LHS of an assign with a typed RHS function.) node.setJSType(type); } void resolve(Scope scope) { node.setJSType(type.resolve(typeParsingErrorReporter, scope)); } } TypedScopeCreator(AbstractCompiler compiler) { this(compiler, compiler.getCodingConvention()); } TypedScopeCreator(AbstractCompiler compiler, CodingConvention codingConvention) { this.compiler = compiler; this.validator = compiler.getTypeValidator(); this.codingConvention = codingConvention; this.typeRegistry = compiler.getTypeRegistry(); this.typeParsingErrorReporter = typeRegistry.getErrorReporter(); this.unknownType = typeRegistry.getNativeObjectType(UNKNOWN_TYPE); } /** * Creates a scope with all types declared. Declares newly discovered types * and type properties in the type registry. */ @Override public Scope createScope(Node root, Scope parent) { // Constructing the global scope is very different than constructing // inner scopes, because only global scopes can contain named classes that // show up in the type registry. Scope newScope = null; AbstractScopeBuilder scopeBuilder = null; if (parent == null) { JSType globalThis = typeRegistry.getNativeObjectType(JSTypeNative.GLOBAL_THIS); // Mark the main root, the externs root, and the src root // with the global this type. root.setJSType(globalThis); root.getFirstChild().setJSType(globalThis); root.getLastChild().setJSType(globalThis); // Run a first-order analysis over the syntax tree. (new FirstOrderFunctionAnalyzer(compiler, functionAnalysisResults)) .process(root.getFirstChild(), root.getLastChild()); // Find

Closure, 120

<FILEB>
<CHANGES>
if (ref.getSymbol().getScope()!= ref.scope) {
return false;
}
<CHANGEE>
<FILEE>
<FILEB> return references.get(i); } } return null; } /** * @return Whether the variable is only assigned a value once for its * lifetime. */ boolean isAssignedOnceInLifetime() { Reference ref = getOneAndOnlyAssignment(); if (ref == null) { return false; } // Make sure this assignment is not in a loop. for (BasicBlock block = ref.getBasicBlock(); block != null; block = block.getParent()) { if (block.isFunction) { <CHANGES> <CHANGEE> break; } else if (block.isLoop) { return false; } } return true; } /** * @return The one and only assignment. Returns if there are 0 or 2+ * assignments. */ private Reference getOneAndOnlyAssignment() { <FILEE> <SCANS> Scope s = Scope.createGlobalScope(root); declareNativeFunctionType(s, ARRAY_FUNCTION_TYPE); declareNativeFunctionType(s, BOOLEAN_OBJECT_FUNCTION_TYPE); declareNativeFunctionType(s, DATE_FUNCTION_TYPE); declareNativeFunctionType(s, ERROR_FUNCTION_TYPE); declareNativeFunctionType(s, EVAL_ERROR_FUNCTION_TYPE); declareNativeFunctionType(s, FUNCTION_FUNCTION_TYPE); declareNativeFunctionType(s, NUMBER_OBJECT_FUNCTION_TYPE); declareNativeFunctionType(s, OBJECT_FUNCTION_TYPE); declareNativeFunctionType(s, RANGE_ERROR_FUNCTION_TYPE); declareNativeFunctionType(s, REFERENCE_ERROR_FUNCTION_TYPE); declareNativeFunctionType(s, REGEXP_FUNCTION_TYPE); declareNativeFunctionType(s, STRING_OBJECT_FUNCTION_TYPE); declareNativeFunctionType(s, SYNTAX_ERROR_FUNCTION_TYPE); declareNativeFunctionType(s, TYPE_ERROR_FUNCTION_TYPE); declareNativeFunctionType(s, URI_ERROR_FUNCTION_TYPE); declareNativeValueType(s, "undefined", VOID_TYPE); // There is no longer a need to special case ActiveXObject // but this remains here until we can get the extern forks // cleaned up. declareNativeValueType(s, "ActiveXObject", FUNCTION_INSTANCE_TYPE); return s; } private void declareNativeFunctionType(Scope scope, JSTypeNative tId) { FunctionType t = typeRegistry.getNativeFunctionType(tId); declareNativeType(scope, t.getInstanceType().getReferenceName(), t); declareNativeType( scope, t.getPrototype().getReferenceName(), t.getPrototype()); } private void declareNativeValueType(Scope scope, String name, JSTypeNative tId) { declareNativeType(scope, name, typeRegistry.getNativeType(tId)); } private static void declareNativeType(Scope scope, String name, JSType t) { scope.declare(name, null, t, null, false); } private static class DiscoverEnumsAndTypedefs extends AbstractShallowStatementCallback { private final JSTypeRegistry registry; DiscoverEnumsAndTypedefs(JSTypeRegistry registry) { this.registry = registry; } @Override public void visit(NodeTraversal t, Node node, Node parent) { switch (node.getType()) { case Token.VAR: for (Node child = node.getFirstChild(); child != null; child = child.getNext()) { identifyNameNode( child, NodeUtil.getBestJSDocInfo(child)); } break; case Token.EXPR_RESULT: Node firstChild = node.getFirstChild(); if (firstChild.isAssign()) { identifyNameNode( firstChild.getFirstChild(), firstChild.getJSDocInfo()); } else { identifyNameNode( firstChild, firstChild.getJSDocInfo()); } break; } } private void identifyNameNode( Node nameNode, JSDocInfo info) { if (nameNode.isQualifiedName()) { if (info != null) { if (info.hasEnumParameterType()) { registry.identifyNonNullableName(nameNode.getQualifiedName()); } else if (info.hasTypedefType()) { registry.identifyNonNullableName(nameNode.getQualifiedName()); } } } } } private JSType getNativeType(JSTypeNative nativeType) { return typeRegistry.getNativeType(nativeType); } private abstract class AbstractScopeBuilder implements NodeTraversal.Callback, Scope.TypeResolver { /** * The scope that we're building. */ final Scope scope; private final List

Closure, 120

<FILEB>
<CHANGES>
if (ref.getSymbol().getScope()!= ref.scope) {
return false;
}
<CHANGEE>
<FILEE>
<FILEB> return references.get(i); } } return null; } /** * @return Whether the variable is only assigned a value once for its * lifetime. */ boolean isAssignedOnceInLifetime() { Reference ref = getOneAndOnlyAssignment(); if (ref == null) { return false; } // Make sure this assignment is not in a loop. for (BasicBlock block = ref.getBasicBlock(); block != null; block = block.getParent()) { if (block.isFunction) { <CHANGES> <CHANGEE> break; } else if (block.isLoop) { return false; } } return true; } /** * @return The one and only assignment. Returns if there are 0 or 2+ * assignments. */ private Reference getOneAndOnlyAssignment() { <FILEE> <SCANS><DeferredSetType> deferredSetTypes = Lists.newArrayList(); /** * Functions that we found in the global scope and not in externs. */ private final List<Node> nonExternFunctions = Lists.newArrayList(); /** * Object literals with a @lends annotation aren't analyzed until we * reach the root of the statement they're defined in. * * This ensures that if there are any @lends annotations on the object * literals, the type on the @lends annotation resolves correctly. * * For more information, see * http://code.google.com/p/closure-compiler/issues/detail?id=314 */ private List<Node> lentObjectLiterals = null; /** * Type-less stubs. * * If at the end of traversal, we still don't have types for these * stubs, then we should declare UNKNOWN types. */ private final List<StubDeclaration> stubDeclarations = Lists.newArrayList(); /** * The current source file that we're in. */ private String sourceName = null; /** * The InputId of the current node. */ private InputId inputId; private AbstractScopeBuilder(Scope scope) { this.scope = scope; } void setDeferredType(Node node, JSType type) { deferredSetTypes.add(new DeferredSetType(node, type)); } @Override public void resolveTypes() { // Resolve types and attach them to nodes. for (DeferredSetType deferred : deferredSetTypes) { deferred.resolve(scope); } // Resolve types and attach them to scope slots. Iterator<Var> vars = scope.getVars(); while (vars.hasNext()) { vars.next().resolveType(typeParsingErrorReporter); } // Tell the type registry that any remaining types // are unknown. typeRegistry.resolveTypesInScope(scope); } @Override public final boolean shouldTraverse(NodeTraversal t, Node n, Node parent) { inputId = t.getInputId(); if (n.isFunction() || n.isScript()) { Preconditions.checkNotNull(inputId); sourceName = NodeUtil.getSourceName(n); } // We do want to traverse the name of a named function, but we don't // want to traverse the arguments or body. boolean descend = parent == null || !parent.isFunction() || n == parent.getFirstChild() || parent == scope.getRootNode(); if (descend) { // Handle hoisted functions on pre-order traversal, so that they // get hit before other things in the scope. if (NodeUtil.isStatementParent(n)) { for (Node child = n.getFirstChild(); child != null; child = child.getNext()) { if (NodeUtil.isHoistedFunctionDeclaration(child)) { defineFunctionLiteral(child); } } } } return descend; } @Override public void visit(NodeTraversal t, Node n, Node parent) { inputId = t.getInputId(); attachLiteralTypes(n); switch (n.getType()) { case Token.CALL: checkForClassDefiningCalls(t, n); checkForCallingConventionDefiningCalls(n, delegateCallingConventions); break; case Token.FUNCTION: if (t.getInput() == null || !t.getInput().isExtern()) { nonExternFunctions.add(n); } // Hoisted functions are handled during pre-traversal. if (!NodeUtil.isHoistedFunctionDeclaration(n)) { defineFunctionLiteral(n); } break; case Token.

Closure, 120

<FILEB>
<CHANGES>
if (ref.getSymbol().getScope()!= ref.scope) {
return false;
}
<CHANGEE>
<FILEE>
<FILEB> return references.get(i); } } return null; } /** * @return Whether the variable is only assigned a value once for its * lifetime. */ boolean isAssignedOnceInLifetime() { Reference ref = getOneAndOnlyAssignment(); if (ref == null) { return false; } // Make sure this assignment is not in a loop. for (BasicBlock block = ref.getBasicBlock(); block != null; block = block.getParent()) { if (block.isFunction) { <CHANGES> <CHANGEE> break; } else if (block.isLoop) { return false; } } return true; } /** * @return The one and only assignment. Returns if there are 0 or 2+ * assignments. */ private Reference getOneAndOnlyAssignment() { <FILEE> <SCANS>ASSIGN: // Handle initialization of properties. Node firstChild = n.getFirstChild(); if (firstChild.isGetProp() && firstChild.isQualifiedName()) { maybeDeclareQualifiedName(t, n.getJSDocInfo(), firstChild, n, firstChild.getNext()); } break; case Token.CATCH: defineCatch(n); break; case Token.VAR: defineVar(n); break; case Token.GETPROP: // Handle stubbed properties. if (parent.isExprResult() && n.isQualifiedName()) { maybeDeclareQualifiedName(t, n.getJSDocInfo(), n, parent, null); } break; } // Analyze any @lends object literals in this statement. if (n.getParent() != null && NodeUtil.isStatement(n) && lentObjectLiterals != null) { for (Node objLit : lentObjectLiterals) { defineObjectLiteral(objLit); } lentObjectLiterals.clear(); } } private void attachLiteralTypes(Node n) { switch (n.getType()) { case Token.NULL: n.setJSType(getNativeType(NULL_TYPE)); break; case Token.VOID: n.setJSType(getNativeType(VOID_TYPE)); break; case Token.STRING: n.setJSType(getNativeType(STRING_TYPE)); break; case Token.NUMBER: n.setJSType(getNativeType(NUMBER_TYPE)); break; case Token.TRUE: case Token.FALSE: n.setJSType(getNativeType(BOOLEAN_TYPE)); break; case Token.REGEXP: n.setJSType(getNativeType(REGEXP_TYPE)); break; case Token.OBJECTLIT: JSDocInfo info = n.getJSDocInfo(); if (info != null && info.getLendsName() != null) { if (lentObjectLiterals == null) { lentObjectLiterals = Lists.newArrayList(); } lentObjectLiterals.add(n); } else { defineObjectLiteral(n); } break; // NOTE(nicksantos): If we ever support Array tuples, // we will need to put ARRAYLIT here as well. } } private void defineObjectLiteral(Node objectLit) { // Handle the @lends annotation. JSType type = null; JSDocInfo info = objectLit.getJSDocInfo(); if (info != null && info.getLendsName() != null) { String lendsName = info.getLendsName(); Var lendsVar = scope.getVar(lendsName); if (lendsVar == null) { compiler.report( JSError.make(sourceName, objectLit, UNKNOWN_LENDS, lendsName)); } else { type = lendsVar.getType(); if (type == null) { type = unknownType; } if (!type.isSubtype(typeRegistry.getNativeType(OBJECT_TYPE))) { compiler.report( JSError.make(sourceName, objectLit, LENDS_ON_NON_OBJECT, lendsName, type.toString())); type = null; } else { objectLit.setJSType(type); } } } info = NodeUtil.getBestJSDocInfo(objectLit); Node lValue = NodeUtil.getBestLValue(objectLit); String lValueName = NodeUtil.getBestLValueName(lValue); boolean createdEnumType = false;

Closure, 120

<FILEB>
<CHANGES>
if (ref.getSymbol().getScope()!= ref.scope) {
return false;
}
<CHANGEE>
<FILEE>
<FILEB> return references.get(i); } } return null; } /** * @return Whether the variable is only assigned a value once for its * lifetime. */ boolean isAssignedOnceInLifetime() { Reference ref = getOneAndOnlyAssignment(); if (ref == null) { return false; } // Make sure this assignment is not in a loop. for (BasicBlock block = ref.getBasicBlock(); block != null; block = block.getParent()) { if (block.isFunction) { <CHANGES> <CHANGEE> break; } else if (block.isLoop) { return false; } } return true; } /** * @return The one and only assignment. Returns if there are 0 or 2+ * assignments. */ private Reference getOneAndOnlyAssignment() { <FILEE> <SCANS> if (info != null && info.hasEnumParameterType()) { type = createEnumTypeFromNodes(objectLit, lValueName, info, lValue); createdEnumType = true; } if (type == null) { type = typeRegistry.createAnonymousObjectType(info); } setDeferredType(objectLit, type); // If this is an enum, the properties were already taken care of above. processObjectLitProperties( objectLit, ObjectType.cast(objectLit.getJSType()), !createdEnumType); } /** * Process an object literal and all the types on it. * @param objLit The OBJECTLIT node. * @param objLitType The type of the OBJECTLIT node. This might be a named * type, because of the lends annotation. * @param declareOnOwner If true, declare properties on the objLitType as * well. If false, the caller should take care of this. */ void processObjectLitProperties( Node objLit, ObjectType objLitType, boolean declareOnOwner) { for (Node keyNode = objLit.getFirstChild(); keyNode != null; keyNode = keyNode.getNext()) { Node value = keyNode.getFirstChild(); String memberName = NodeUtil.getObjectLitKeyName(keyNode); JSDocInfo info = keyNode.getJSDocInfo(); JSType valueType = getDeclaredType(info, keyNode, value); JSType keyType = objLitType.isEnumType() ? objLitType.toMaybeEnumType().getElementsType() : NodeUtil.getObjectLitKeyTypeFromValueType(keyNode, valueType); // Try to declare this property in the current scope if it // has an authoritative name. String qualifiedName = NodeUtil.getBestLValueName(keyNode); if (qualifiedName != null) { boolean inferred = keyType == null; defineSlot(keyNode, objLit, qualifiedName, keyType, inferred); } else if (keyType != null) { setDeferredType(keyNode, keyType); } if (keyType != null && objLitType != null && declareOnOwner) { // Declare this property on its object literal. objLitType.defineDeclaredProperty(memberName, keyType, keyNode); } } } /** * Returns the type specified in a JSDoc annotation near a GETPROP or NAME. * * Extracts type information from either the {@code @type} tag or from * the {@code @return} and {@code @param} tags. */ private JSType getDeclaredTypeInAnnotation(Node node, JSDocInfo info) { JSType jsType = null; if (info != null) { if (info.hasType()) { ImmutableList<TemplateType> ownerTypeKeys = ImmutableList.of(); Node ownerNode = NodeUtil.getBestLValueOwner(node); String ownerName = NodeUtil.getBestLValueName(ownerNode); ObjectType ownerType = null; if (ownerName != null) { Var ownerVar = scope.getVar(ownerName); if (ownerVar != null) { ownerType = getPrototypeOwnerType( ObjectType.cast(ownerVar.getType())); if (ownerType != null) { ownerTypeKeys = ownerType.getTemplateTypeMap().getTemplateKeys(); } } } if (!ownerTypeKeys.isEmpty()) { typeRegistry.setTemplateTypeNames(ownerTypeKeys); } jsType = info.getType().evaluate(scope, typeRegistry); if (!ownerTypeKeys.isEmpty()) { typeRegistry.clearTemplateTypeNames(); } } else if (FunctionTypeBuilder

Closure, 120

<FILEB>
<CHANGES>
if (ref.getSymbol().getScope()!= ref.scope) {
return false;
}
<CHANGEE>
<FILEE>
<FILEB> return references.get(i); } } return null; } /** * @return Whether the variable is only assigned a value once for its * lifetime. */ boolean isAssignedOnceInLifetime() { Reference ref = getOneAndOnlyAssignment(); if (ref == null) { return false; } // Make sure this assignment is not in a loop. for (BasicBlock block = ref.getBasicBlock(); block != null; block = block.getParent()) { if (block.isFunction) { <CHANGES> <CHANGEE> break; } else if (block.isLoop) { return false; } } return true; } /** * @return The one and only assignment. Returns if there are 0 or 2+ * assignments. */ private Reference getOneAndOnlyAssignment() { <FILEE> <SCANS> function is inferred. */ private boolean shouldUseFunctionLiteralType( FunctionType type, JSDocInfo info, Node lValue) { if (info != null) { return true; } if (lValue != null && NodeUtil.isObjectLitKey(lValue)) { return false; } return scope.isGlobal() || !type.isReturnTypeInferred(); } /** * Creates a new function type, based on the given nodes. * * This handles two cases that are semantically very different, but * are not mutually exclusive: * - A function literal that needs a type attached to it. * - An assignment expression with function-type info in the JsDoc. * * All parameters are optional, and we will do the best we can to create * a function type. * * This function will always create a function type, so only call it if * you're sure that's what you want. * * @param rValue The function node. * @param name the function's name * @param info the {@link JSDocInfo} attached to the function definition * @param lvalueNode The node where this function is being * assigned. For example, {@code A.prototype.foo = ...} would be used to * determine that this function is a method of A.prototype. May be * null to indicate that this is not being assigned to a qualified name. */ private FunctionType createFunctionTypeFromNodes( @Nullable Node rValue, @Nullable String name, @Nullable JSDocInfo info, @Nullable Node lvalueNode) { FunctionType functionType = null; // Global ctor aliases should be registered with the type registry. if (rValue != null && rValue.isQualifiedName() && scope.isGlobal()) { Var var = scope.getVar(rValue.getQualifiedName()); if (var != null && var.getType() != null && var.getType().isFunctionType()) { FunctionType aliasedType = var.getType().toMaybeFunctionType(); if ((aliasedType.isConstructor() || aliasedType.isInterface()) && !aliasedType.isNativeObjectType()) { functionType = aliasedType; if (name != null && scope.isGlobal()) { typeRegistry.declareType(name, functionType.getInstanceType()); } } } } if (functionType == null) { Node errorRoot = rValue == null ? lvalueNode : rValue; boolean isFnLiteral = rValue != null && rValue.isFunction(); Node fnRoot = isFnLiteral ? rValue : null; Node parametersNode = isFnLiteral ? rValue.getFirstChild().getNext() : null; if (info != null && info.hasType()) { JSType type = info.getType().evaluate(scope, typeRegistry); // Known to be not null since we have the FUNCTION token there. type = type.restrictByNotNullOrUndefined(); if (type.isFunctionType()) { functionType = type.toMaybeFunctionType(); functionType.setJSDocInfo(info); } } if (functionType == null) { // Find the type of any overridden function. Node ownerNode = NodeUtil.getBestLValueOwner(lvalueNode); String ownerName = NodeUtil.getBestLValueName(ownerNode); Var ownerVar = null; String propName = null; ObjectType ownerType = null; if (ownerName != null) { ownerVar = scope.getVar(ownerName); if (ownerVar != null) { ownerType = ObjectType.cast(ownerVar.getType()); } if (name != null) {

Closure, 120

<FILEB>
<CHANGES>
if (ref.getSymbol().getScope()!= ref.scope) {
return false;
}
<CHANGEE>
<FILEE>
<FILEB> return references.get(i); } } return null; } /** * @return Whether the variable is only assigned a value once for its * lifetime. */ boolean isAssignedOnceInLifetime() { Reference ref = getOneAndOnlyAssignment(); if (ref == null) { return false; } // Make sure this assignment is not in a loop. for (BasicBlock block = ref.getBasicBlock(); block != null; block = block.getParent()) { if (block.isFunction) { <CHANGES> <CHANGEE> break; } else if (block.isLoop) { return false; } } return true; } /** * @return The one and only assignment. Returns if there are 0 or 2+ * assignments. */ private Reference getOneAndOnlyAssignment() { <FILEE> <SCANS> propName = name.substring(ownerName.length() + 1); } } ObjectType prototypeOwner = getPrototypeOwnerType(ownerType); TemplateTypeMap prototypeOwnerTypeMap = null; if (prototypeOwner != null && prototypeOwner.getTypeOfThis() != null) { prototypeOwnerTypeMap = prototypeOwner.getTypeOfThis().getTemplateTypeMap(); } FunctionType overriddenType = null; if (ownerType != null && propName != null) { overriddenType = findOverriddenFunction( ownerType, propName, prototypeOwnerTypeMap); } FunctionTypeBuilder builder = new FunctionTypeBuilder(name, compiler, errorRoot, sourceName, scope) .setContents(getFunctionAnalysisResults(fnRoot)) .inferFromOverriddenFunction(overriddenType, parametersNode) .inferTemplateTypeName(info, prototypeOwner) .inferReturnType(info) .inferInheritance(info); // Infer the context type. boolean searchedForThisType = false; if (ownerType != null && ownerType.isFunctionPrototypeType() && ownerType.getOwnerFunction().hasInstanceType()) { builder.inferThisType( info, ownerType.getOwnerFunction().getInstanceType()); searchedForThisType = true; } else if (ownerNode != null && ownerNode.isThis()) { // If 'this' has a type, use that instead. // This is a hack, necessary because CollectProperties (below) // doesn't run with the scope that it's building, // so scope.getTypeOfThis() will be wrong. JSType injectedThisType = ownerNode.getJSType(); builder.inferThisType( info, injectedThisType == null ? scope.getTypeOfThis() : injectedThisType); searchedForThisType = true; } if (!searchedForThisType) { builder.inferThisType(info); } functionType = builder .inferParameterTypes(parametersNode, info) .buildAndRegister(); } } // all done return functionType; } private ObjectType getPrototypeOwnerType(ObjectType ownerType) { if (ownerType != null && ownerType.isFunctionPrototypeType()) { return ownerType.getOwnerFunction(); } return null; } /** * Find the function that's being overridden on this type, if any. */ private FunctionType findOverriddenFunction( ObjectType ownerType, String propName, TemplateTypeMap typeMap) { FunctionType result = null; // First, check to see if the property is implemented // on a superclass. JSType propType = ownerType.getPropertyType(propName); if (propType != null && propType.isFunctionType()) { result = propType.toMaybeFunctionType(); } else { // If it's not, then check to see if it's implemented // on an implemented interface. for (ObjectType iface : ownerType.getCtorImplementedInterfaces()) { propType = iface.getPropertyType(propName); if (propType != null && propType.isFunctionType()) { result = propType.toMaybeFunctionType(); break; } } } if (result != null && typeMap != null && !typeMap.isEmpty()) { result = result.visit( new TemplateTypeMapReplacer(typeRegistry, typeMap)) .toMaybeFunctionType(); } return result; } /** * Creates a new enum type, based on the given nodes. * * This handles two cases that are semantically very different, but * are not mutually exclusive: * - An object literal that needs an enum type attached to it. * - An assignment expression with an enum tag in the JsDoc. * *

Closure, 120

<FILEB>
<CHANGES>
if (ref.getSymbol().getScope()!= ref.scope) {
return false;
}
<CHANGEE>
<FILEE>
<FILEB> return references.get(i); } } return null; } /** * @return Whether the variable is only assigned a value once for its * lifetime. */ boolean isAssignedOnceInLifetime() { Reference ref = getOneAndOnlyAssignment(); if (ref == null) { return false; } // Make sure this assignment is not in a loop. for (BasicBlock block = ref.getBasicBlock(); block != null; block = block.getParent()) { if (block.isFunction) { <CHANGES> <CHANGEE> break; } else if (block.isLoop) { return false; } } return true; } /** * @return The one and only assignment. Returns if there are 0 or 2+ * assignments. */ private Reference getOneAndOnlyAssignment() { <FILEE> <SCANS> This function will always create an enum type, so only call it if * you're sure that's what you want. * * @param rValue The node of the enum. * @param name The enum's name * @param info The {@link JSDocInfo} attached to the enum definition. * @param lValueNode The node where this function is being * assigned. */ private EnumType createEnumTypeFromNodes(Node rValue, String name, JSDocInfo info, Node lValueNode) { Preconditions.checkNotNull(info); Preconditions.checkState(info.hasEnumParameterType()); EnumType enumType = null; if (rValue != null && rValue.isQualifiedName()) { // Handle an aliased enum. Var var = scope.getVar(rValue.getQualifiedName()); if (var != null && var.getType() instanceof EnumType) { enumType = (EnumType) var.getType(); } } if (enumType == null) { JSType elementsType = info.getEnumParameterType().evaluate(scope, typeRegistry); enumType = typeRegistry.createEnumType(name, rValue, elementsType); if (rValue != null && rValue.isObjectLit()) { // collect enum elements Node key = rValue.getFirstChild(); while (key != null) { String keyName = NodeUtil.getStringValue(key); if (keyName == null) { // GET and SET don't have a String value; compiler.report( JSError.make(sourceName, key, ENUM_NOT_CONSTANT, keyName)); } else if (!codingConvention.isValidEnumKey(keyName)) { compiler.report( JSError.make(sourceName, key, ENUM_NOT_CONSTANT, keyName)); } else { enumType.defineElement(keyName, key); } key = key.getNext(); } } } if (name != null && scope.isGlobal()) { typeRegistry.declareType(name, enumType.getElementsType()); } return enumType; } /** * Defines a typed variable. The defining node will be annotated with the * variable's type or {@code null} if its type is inferred. * @param name the defining node. It must be a {@link Token#NAME}. * @param parent the {@code name}'s parent. * @param type the variable's type. It may be {@code null}, in which case * the variable's type will be inferred. */ private void defineSlot(Node name, Node parent, JSType type) { defineSlot(name, parent, type, type == null); } /** * Defines a typed variable. The defining node will be annotated with the * variable's type of {@link JSTypeNative#UNKNOWN_TYPE} if its type is * inferred. * * Slots may be any variable or any qualified name in the global scope. * * @param n the defining NAME or GETPROP node. * @param parent the {@code n}'s parent. * @param type the variable's type. It may be {@code null} if * {@code inferred} is {@code true}. */ void defineSlot(Node n, Node parent, JSType type, boolean inferred) { Preconditions.checkArgument(inferred || type != null); // Only allow declarations of NAMEs and qualified names. // Object literal keys will have to compute their names themselves. if (n.isName()) { Preconditions.checkArgument( parent.isFunction() || parent.isVar() || parent.isParamList() || parent.isCatch()); } else { Preconditions.check

Closure, 120

<FILEB>
<CHANGES>
if (ref.getSymbol().getScope()!= ref.scope) {
return false;
}
<CHANGEE>
<FILEE>
<FILEB> return references.get(i); } } return null; } /** * @return Whether the variable is only assigned a value once for its * lifetime. */ boolean isAssignedOnceInLifetime() { Reference ref = getOneAndOnlyAssignment(); if (ref == null) { return false; } // Make sure this assignment is not in a loop. for (BasicBlock block = ref.getBasicBlock(); block != null; block = block.getParent()) { if (block.isFunction) { <CHANGES> <CHANGEE> break; } else if (block.isLoop) { return false; } } return true; } /** * @return The one and only assignment. Returns if there are 0 or 2+ * assignments. */ private Reference getOneAndOnlyAssignment() { <FILEE> <SCANS>Argument( n.isGetProp() && (parent.isAssign() || parent.isExprResult())); } defineSlot(n, parent, n.getQualifiedName(), type, inferred); } /** * Defines a symbol in the current scope. * * @param n the defining NAME or GETPROP or object literal key node. * @param parent the {@code n}'s parent. * @param variableName The name that this should be known by. * @param type the variable's type. It may be {@code null} if * {@code inferred} is {@code true}. * @param inferred Whether the type is inferred or declared. */ void defineSlot(Node n, Node parent, String variableName, JSType type, boolean inferred) { Preconditions.checkArgument(!variableName.isEmpty()); boolean isGlobalVar = n.isName() && scope.isGlobal(); boolean shouldDeclareOnGlobalThis = isGlobalVar && (parent.isVar() || parent.isFunction()); // If n is a property, then we should really declare it in the // scope where the root object appears. This helps out people // who declare "global" names in an anonymous namespace. Scope scopeToDeclareIn = scope; if (n.isGetProp() && !scope.isGlobal() && isQnameRootedInGlobalScope(n)) { Scope globalScope = scope.getGlobalScope(); // don't try to declare in the global scope if there's // already a symbol there with this name. if (!globalScope.isDeclared(variableName, false)) { scopeToDeclareIn = scope.getGlobalScope(); } } // The input may be null if we are working with a AST snippet. So read // the extern info from the node. Var newVar = null; // declared in closest scope? CompilerInput input = compiler.getInput(inputId); if (scopeToDeclareIn.isDeclared(variableName, false)) { Var oldVar = scopeToDeclareIn.getVar(variableName); newVar = validator.expectUndeclaredVariable( sourceName, input, n, parent, oldVar, variableName, type); } else { if (type != null) { setDeferredType(n, type); } newVar = scopeToDeclareIn.declare(variableName, n, type, input, inferred); if (type instanceof EnumType) { Node initialValue = newVar.getInitialValue(); boolean isValidValue = initialValue != null && (initialValue.isObjectLit() || initialValue.isQualifiedName()); if (!isValidValue) { compiler.report(JSError.make(sourceName, n, ENUM_INITIALIZER)); } } } // We need to do some additional work for constructors and interfaces. FunctionType fnType = JSType.toMaybeFunctionType(type); if (fnType != null && // We don't want to look at empty function types. !type.isEmptyType()) { // We want to make sure that when we declare a new instance type // (with @constructor) that there's actually a ctor for it. // This doesn't apply to structural constructors (like // function(new:Array). Checking the constructed type against // the variable name is a sufficient check for this. if ((fnType.isConstructor() || fnType.isInterface()) && variableName.equals(fnType.getReferenceName())) { finishConstructorDefinition(n, variableName, fnType, scopeToDeclareIn, input, newVar); } } if (shouldDeclareOnGlobalThis) { ObjectType globalThis = typeRegistry.getNativeObjectType(GLOBAL_THIS); if (in

Closure, 120

<FILEB>
<CHANGES>
if (ref.getSymbol().getScope()!= ref.scope) {
return false;
}
<CHANGEE>
<FILEE>
<FILEB> return references.get(i); } } return null; } /** * @return Whether the variable is only assigned a value once for its * lifetime. */ boolean isAssignedOnceInLifetime() { Reference ref = getOneAndOnlyAssignment(); if (ref == null) { return false; } // Make sure this assignment is not in a loop. for (BasicBlock block = ref.getBasicBlock(); block != null; block = block.getParent()) { if (block.isFunction) { <CHANGES> <CHANGEE> break; } else if (block.isLoop) { return false; } } return true; } /** * @return The one and only assignment. Returns if there are 0 or 2+ * assignments. */ private Reference getOneAndOnlyAssignment() { <FILEE> <SCANS>ferred) { globalThis.defineInferredProperty(variableName, type == null ? getNativeType(JSTypeNative.NO_TYPE) : type, n); } else { globalThis.defineDeclaredProperty(variableName, type, n); } } if (isGlobalVar && "Window".equals(variableName) && type != null && type.isFunctionType() && type.isConstructor()) { FunctionType globalThisCtor = typeRegistry.getNativeObjectType(GLOBAL_THIS).getConstructor(); globalThisCtor.getInstanceType().clearCachedValues(); globalThisCtor.getPrototype().clearCachedValues(); globalThisCtor .setPrototypeBasedOn((type.toMaybeFunctionType()).getInstanceType()); } } private void finishConstructorDefinition( Node n, String variableName, FunctionType fnType, Scope scopeToDeclareIn, CompilerInput input, Var newVar) { // Declare var.prototype in the scope chain. FunctionType superClassCtor = fnType.getSuperClassConstructor(); Property prototypeSlot = fnType.getSlot("prototype"); // When we declare the function prototype implicitly, we // want to make sure that the function and its prototype // are declared at the same node. We also want to make sure // that the if a symbol has both a Var and a JSType, they have // the same node. // // This consistency is helpful to users of SymbolTable, // because everything gets declared at the same place. prototypeSlot.setNode(n); String prototypeName = variableName + ".prototype"; // There are some rare cases where the prototype will already // be declared. See TypedScopeCreatorTest#testBogusPrototypeInit. // Fortunately, other warnings will complain if this happens. Var prototypeVar = scopeToDeclareIn.getVar(prototypeName); if (prototypeVar != null && prototypeVar.scope == scopeToDeclareIn) { scopeToDeclareIn.undeclare(prototypeVar); } scopeToDeclareIn.declare(prototypeName, n, prototypeSlot.getType(), input, /* declared iff there's an explicit supertype */ superClassCtor == null || superClassCtor.getInstanceType().isEquivalentTo( getNativeType(OBJECT_TYPE))); // Make sure the variable is initialized to something if // it constructs itself. if (newVar.getInitialValue() == null && !n.isFromExterns()) { compiler.report( JSError.make(sourceName, n, fnType.isConstructor() ? CTOR_INITIALIZER : IFACE_INITIALIZER, variableName)); } } /** * Check if the given node is a property of a name in the global scope. */ private boolean isQnameRootedInGlobalScope(Node n) { Scope scope = getQnameRootScope(n); return scope != null && scope.isGlobal(); } /** * Return the scope for the name of the given node. */ private Scope getQnameRootScope(Node n) { Node root = NodeUtil.getRootOfQualifiedName(n); if (root.isName()) { Var var = scope.getVar(root.getString()); if (var != null) { return var.getScope(); } } return null; } /** * Look for a type declaration on a property assignment * (in an ASSIGN or an object literal key). * @param info The doc info for this property. * @param lValue The l-value node. * @param rValue The node that {@code n} is being initialized to, * or {@code null} if this is a stub declaration. */ private JSType get

Closure, 120

<FILEB>
<CHANGES>
if (ref.getSymbol().getScope()!= ref.scope) {
return false;
}
<CHANGEE>
<FILEE>
<FILEB> return references.get(i); } } return null; } /** * @return Whether the variable is only assigned a value once for its * lifetime. */ boolean isAssignedOnceInLifetime() { Reference ref = getOneAndOnlyAssignment(); if (ref == null) { return false; } // Make sure this assignment is not in a loop. for (BasicBlock block = ref.getBasicBlock(); block != null; block = block.getParent()) { if (block.isFunction) { <CHANGES> <CHANGEE> break; } else if (block.isLoop) { return false; } } return true; } /** * @return The one and only assignment. Returns if there are 0 or 2+ * assignments. */ private Reference getOneAndOnlyAssignment() { <FILEE> <SCANS>DeclaredType(JSDocInfo info, Node lValue, @Nullable Node rValue) { if (info != null && info.hasType()) { return getDeclaredTypeInAnnotation(lValue, info); } else if (rValue != null && rValue.isFunction() && shouldUseFunctionLiteralType( JSType.toMaybeFunctionType(rValue.getJSType()), info, lValue)) { return rValue.getJSType(); } else if (info != null) { if (info.hasEnumParameterType()) { if (rValue != null && rValue.isObjectLit()) { return rValue.getJSType(); } else { return createEnumTypeFromNodes( rValue, lValue.getQualifiedName(), info, lValue); } } else if (info.isConstructor() || info.isInterface()) { return createFunctionTypeFromNodes( rValue, lValue.getQualifiedName(), info, lValue); } } // Check if this is constant, and if it has a known type. if (isConstantSymbol(info, lValue)) { if (rValue != null) { JSDocInfo rValueInfo = rValue.getJSDocInfo(); if (rValueInfo != null && rValueInfo.hasType()) { // If rValue has a type-cast, we use the type in the type-cast. return rValueInfo.getType().evaluate(scope, typeRegistry); } else if (rValue.getJSType() != null && !rValue.getJSType().isUnknownType()) { // If rValue's type was already computed during scope creation, // then we can safely use that. return rValue.getJSType(); } else if (rValue.isOr()) { // Check for a very specific JS idiom: // var x = x || TYPE; // This is used by Closure's base namespace for esoteric // reasons. Node firstClause = rValue.getFirstChild(); Node secondClause = firstClause.getNext(); boolean namesMatch = firstClause.isName() && lValue.isName() && firstClause.getString().equals(lValue.getString()); if (namesMatch && secondClause.getJSType() != null && !secondClause.getJSType().isUnknownType()) { return secondClause.getJSType(); } } } } return getDeclaredTypeInAnnotation(lValue, info); } private FunctionType getFunctionType(@Nullable Var v) { JSType t = v == null ? null : v.getType(); ObjectType o = t == null ? null : t.dereference(); return JSType.toMaybeFunctionType(o); } /** * Look for calls that set a delegate method's calling convention. */ private void checkForCallingConventionDefiningCalls( Node n, Map<String, String> delegateCallingConventions) { codingConvention.checkForCallingConventionDefiningCalls(n, delegateCallingConventions); } /** * Look for class-defining calls. * Because JS has no 'native' syntax for defining classes, * this is often very coding-convention dependent and business-logic heavy. */ private void checkForClassDefiningCalls(NodeTraversal t, Node n) { SubclassRelationship relationship = codingConvention.getClassesDefinedByCall(n); if (relationship != null) { FunctionType superCtor = getFunctionType( scope.getVar(relationship.superclassName)); FunctionType subCtor = getFunctionType( scope.getVar(relationship.subclassName)); if (superCtor != null && superCtor.isConstructor() && subCtor != null && subCtor.isConstructor()) {

Closure, 120

<FILEB>
<CHANGES>
if (ref.getSymbol().getScope()!= ref.scope) {
return false;
}
<CHANGEE>
<FILEE>
<FILEB> return references.get(i); } } return null; } /** * @return Whether the variable is only assigned a value once for its * lifetime. */ boolean isAssignedOnceInLifetime() { Reference ref = getOneAndOnlyAssignment(); if (ref == null) { return false; } // Make sure this assignment is not in a loop. for (BasicBlock block = ref.getBasicBlock(); block != null; block = block.getParent()) { if (block.isFunction) { <CHANGES> <CHANGEE> break; } else if (block.isLoop) { return false; } } return true; } /** * @return The one and only assignment. Returns if there are 0 or 2+ * assignments. */ private Reference getOneAndOnlyAssignment() { <FILEE> <SCANS> delegateProxy, findDelegate); delegateProxyPrototypes.add(delegateProxy.getPrototype()); } } } /** * Declare the symbol for a qualified name in the global scope. * * @param info The doc info for this property. * @param n A top-level GETPROP node (it should not be contained inside * another GETPROP). * @param parent The parent of {@code n}. * @param rhsValue The node that {@code n} is being initialized to, * or {@code null} if this is a stub declaration. */ void maybeDeclareQualifiedName(NodeTraversal t, JSDocInfo info, Node n, Node parent, Node rhsValue) { Node ownerNode = n.getFirstChild(); String ownerName = ownerNode.getQualifiedName(); String qName = n.getQualifiedName(); String propName = n.getLastChild().getString(); Preconditions.checkArgument(qName != null && ownerName != null); // Precedence of type information on GETPROPs: // 1) @type annotation / @enum annotation // 2) ASSIGN to FUNCTION literal // 3) @param/@return annotation (with no function literal) // 4) ASSIGN to something marked @const // 5) ASSIGN to anything else // // 1, 3, and 4 are declarations, 5 is inferred, and 2 is a declaration iff // the function has JsDoc or has not been declared before. // // FUNCTION literals are special because TypedScopeCreator is very smart // about getting as much type information as possible for them. // Determining type for #1 + #2 + #3 + #4 JSType valueType = getDeclaredType(info, n, rhsValue); if (valueType == null && rhsValue != null) { // Determining type for #5 valueType = rhsValue.getJSType(); } // Function prototypes are special. // It's a common JS idiom to do: // F.prototype = { ... }; // So if F does not have an explicitly declared super type, // allow F.prototype to be redefined arbitrarily. if ("prototype".equals(propName)) { Var qVar = scope.getVar(qName); if (qVar != null) { // If the programmer has declared that F inherits from Super, // and they assign F.prototype to an object literal, // then they are responsible for making sure that the object literal's // implicit prototype is set up appropriately. We just obey // the @extends tag. ObjectType qVarType = ObjectType.cast(qVar.getType()); if (qVarType != null && rhsValue != null && rhsValue.isObjectLit()) { typeRegistry.resetImplicitPrototype( rhsValue.getJSType(), qVarType.getImplicitPrototype()); } else if (!qVar.isTypeInferred()) { // If the programmer has declared that F inherits from Super, // and they assign F.prototype to some arbitrary expression, // there's not much we can do. We just ignore the expression, // and hope they've annotated their code in a way to tell us // what props are going to be on that prototype. return; } qVar.getScope().undeclare(qVar); } } if (valueType == null) { if (parent.isExprResult()) { stubDeclarations.add(new StubDeclaration( n, t.getInput() != null && t.getInput().isExtern(), ownerName)); } return; } boolean inferred = isQualifiedNameInferred( qName, n, info, rhsValue, valueType); if (!inferred

Closure, 120

<FILEB>
<CHANGES>
if (ref.getSymbol().getScope()!= ref.scope) {
return false;
}
<CHANGEE>
<FILEE>
<FILEB> return references.get(i); } } return null; } /** * @return Whether the variable is only assigned a value once for its * lifetime. */ boolean isAssignedOnceInLifetime() { Reference ref = getOneAndOnlyAssignment(); if (ref == null) { return false; } // Make sure this assignment is not in a loop. for (BasicBlock block = ref.getBasicBlock(); block != null; block = block.getParent()) { if (block.isFunction) { <CHANGES> <CHANGEE> break; } else if (block.isLoop) { return false; } } return true; } /** * @return The one and only assignment. Returns if there are 0 or 2+ * assignments. */ private Reference getOneAndOnlyAssignment() { <FILEE> <SCANS>) { ObjectType ownerType = getObjectSlot(ownerName); if (ownerType != null) { // Only declare this as an official property if it has not been // declared yet. boolean isExtern = t.getInput() != null && t.getInput().isExtern(); if ((!ownerType.hasOwnProperty(propName) || ownerType.isPropertyTypeInferred(propName)) && ((isExtern && !ownerType.isNativeObjectType()) || !ownerType.isInstanceType())) { // If the property is undeclared or inferred, declare it now. ownerType.defineDeclaredProperty(propName, valueType, n); } } // If the property is already declared, the error will be // caught when we try to declare it in the current scope. defineSlot(n, parent, valueType, inferred); } else if (rhsValue != null && rhsValue.isTrue()) { // We declare these for delegate proxy method properties. ObjectType ownerType = getObjectSlot(ownerName); FunctionType ownerFnType = JSType.toMaybeFunctionType(ownerType); if (ownerFnType != null) { JSType ownerTypeOfThis = ownerFnType.getTypeOfThis(); String delegateName = codingConvention.getDelegateSuperclassName(); JSType delegateType = delegateName == null ? null : typeRegistry.getType(delegateName); if (delegateType != null && ownerTypeOfThis.isSubtype(delegateType)) { defineSlot(n, parent, getNativeType(BOOLEAN_TYPE), true); } } } } /** * Determines whether a qualified name is inferred. * NOTE(nicksantos): Determining whether a property is declared or not * is really really obnoxious. * * The problem is that there are two (equally valid) coding styles: * * (function() { * /* The authoritative definition of goog.bar. / * goog.bar = function() {}; * })(); * * function f() { * goog.bar(); * /* Reset goog.bar to a no-op. / * goog.bar = function() {}; * } * * In a dynamic language with first-class functions, it's very difficult * to know which one the user intended without looking at lots of * contextual information (the second example demonstrates a small case * of this, but there are some really pathological cases as well). * * The current algorithm checks if either the declaration has * JsDoc type information, or @const with a known type, * or a function literal with a name we haven't seen before. */ private boolean isQualifiedNameInferred( String qName, Node n, JSDocInfo info, Node rhsValue, JSType valueType) { if (valueType == null) { return true; } // Prototypes of constructors and interfaces are always declared. if (qName != null && qName.endsWith(".prototype")) { String className = qName.substring(0, qName.lastIndexOf(".prototype")); Var slot = scope.getSlot(className); JSType classType = slot == null ? null : slot.getType(); if (classType != null && (classType.isConstructor() || classType.isInterface())) { return false; } } boolean inferred = true; if (info != null) { inferred = !(info.hasType() || info.hasEnumParameterType() || (isConstantSymbol(info, n) && valueType != null && !valueType.isUnknownType()) || FunctionTypeBuilder.isFunctionTypeDeclaration(info)); } if (inferred && rhsValue != null && rhsValue.isFunction

Closure, 120

<FILEB>
<CHANGES>
if (ref.getSymbol().getScope()!= ref.scope) {
return false;
}
<CHANGEE>
<FILEE>
<FILEB> return references.get(i); } } return null; } /** * @return Whether the variable is only assigned a value once for its * lifetime. */ boolean isAssignedOnceInLifetime() { Reference ref = getOneAndOnlyAssignment(); if (ref == null) { return false; } // Make sure this assignment is not in a loop. for (BasicBlock block = ref.getBasicBlock(); block != null; block = block.getParent()) { if (block.isFunction) { <CHANGES> <CHANGEE> break; } else if (block.isLoop) { return false; } } return true; } /** * @return The one and only assignment. Returns if there are 0 or 2+ * assignments. */ private Reference getOneAndOnlyAssignment() { <FILEE> <SCANS>()) { if (info != null) { return false; } else if (!scope.isDeclared(qName, false) && n.isUnscopedQualifiedName()) { // Check if this is in a conditional block. // Functions assigned in conditional blocks are inferred. for (Node current = n.getParent(); !(current.isScript() || current.isFunction()); current = current.getParent()) { if (NodeUtil.isControlStructure(current)) { return true; } } // Check if this is assigned in an inner scope. // Functions assigned in inner scopes are inferred. AstFunctionContents contents = getFunctionAnalysisResults(scope.getRootNode()); if (contents == null || !contents.getEscapedQualifiedNames().contains(qName)) { return false; } } } return inferred; } private boolean isConstantSymbol(JSDocInfo info, Node node) { if (info != null && info.isConstant()) { return true; } switch (node.getType()) { case Token.NAME: return NodeUtil.isConstantByConvention( compiler.getCodingConvention(), node, node.getParent()); case Token.GETPROP: return node.isQualifiedName() && NodeUtil.isConstantByConvention( compiler.getCodingConvention(), node.getLastChild(), node); } return false; } /** * Find the ObjectType associated with the given slot. * @param slotName The name of the slot to find the type in. * @return An object type, or null if this slot does not contain an object. */ private ObjectType getObjectSlot(String slotName) { Var ownerVar = scope.getVar(slotName); if (ownerVar != null) { JSType ownerVarType = ownerVar.getType(); return ObjectType.cast(ownerVarType == null ? null : ownerVarType.restrictByNotNullOrUndefined()); } return null; } /** * Resolve any stub declarations to unknown types if we could not * find types for them during traversal. */ void resolveStubDeclarations() { for (StubDeclaration stub : stubDeclarations) { Node n = stub.node; Node parent = n.getParent(); String qName = n.getQualifiedName(); String propName = n.getLastChild().getString(); String ownerName = stub.ownerName; boolean isExtern = stub.isExtern; if (scope.isDeclared(qName, false)) { continue; } // If we see a stub property, make sure to register this property // in the type registry. ObjectType ownerType = getObjectSlot(ownerName); defineSlot(n, parent, unknownType, true); if (ownerType != null && (isExtern || ownerType.isFunctionPrototypeType())) { // If this is a stub for a prototype, just declare it // as an unknown type. These are seen often in externs. ownerType.defineInferredProperty( propName, unknownType, n); } else { typeRegistry.registerPropertyOnType( propName, ownerType == null ? unknownType : ownerType); } } } /** * Collects all declared properties in a function, and * resolves them relative to the global scope. */ private final class CollectProperties extends AbstractShallowStatementCallback { private final JSType thisType; CollectProperties(JSType thisType) { this.thisType = thisType; } @Override public void visit(NodeTraversal t, Node n, Node parent) { if (n.isExprResult()) { Node child = n.getFirstChild(); switch (child.getType()) { case Token.

Closure, 120

<FILEB>
<CHANGES>
if (ref.getSymbol().getScope()!= ref.scope) {
return false;
}
<CHANGEE>
<FILEE>
<FILEB> return references.get(i); } } return null; } /** * @return Whether the variable is only assigned a value once for its * lifetime. */ boolean isAssignedOnceInLifetime() { Reference ref = getOneAndOnlyAssignment(); if (ref == null) { return false; } // Make sure this assignment is not in a loop. for (BasicBlock block = ref.getBasicBlock(); block != null; block = block.getParent()) { if (block.isFunction) { <CHANGES> <CHANGEE> break; } else if (block.isLoop) { return false; } } return true; } /** * @return The one and only assignment. Returns if there are 0 or 2+ * assignments. */ private Reference getOneAndOnlyAssignment() { <FILEE> <SCANS>ASSIGN: maybeCollectMember(child.getFirstChild(), child, child.getLastChild()); break; case Token.GETPROP: maybeCollectMember(child, child, null); break; } } } private void maybeCollectMember(Node member, Node nodeWithJsDocInfo, @Nullable Node value) { JSDocInfo info = nodeWithJsDocInfo.getJSDocInfo(); // Do nothing if there is no JSDoc type info, or // if the node is not a member expression, or // if the member expression is not of the form: this.someProperty. if (info == null || !member.isGetProp() || !member.getFirstChild().isThis()) { return; } member.getFirstChild().setJSType(thisType); // TODO(johnlenz): We are evaluating these values in the wrong scope: // https://code.google.com/p/closure-compiler/issues/detail?id=926 JSType thisObjectType = thisType.toObjectType(); if (thisObjectType != null) { ImmutableList<TemplateType> keys = thisObjectType.getTemplateTypeMap().getTemplateKeys(); typeRegistry.setTemplateTypeNames(keys); } JSType jsType = getDeclaredType(info, member, value); if (thisObjectType != null) { typeRegistry.clearTemplateTypeNames(); } Node name = member.getLastChild(); if (jsType != null && (name.isName() || name.isString()) && thisType.toObjectType() != null) { thisType.toObjectType().defineDeclaredProperty( name.getString(), jsType, member); } } } // end CollectProperties } /** * A stub declaration without any type information. */ private static final class StubDeclaration { private final Node node; private final boolean isExtern; private final String ownerName; private StubDeclaration(Node node, boolean isExtern, String ownerName) { this.node = node; this.isExtern = isExtern; this.ownerName = ownerName; } } /** * A shallow traversal of the global scope to build up all classes, * functions, and methods. */ private final class GlobalScopeBuilder extends AbstractScopeBuilder { private GlobalScopeBuilder(Scope scope) { super(scope); } /** * Visit a node in the global scope, and add anything it declares to the * global symbol table. * * @param t The current traversal. * @param n The node being visited. * @param parent The parent of n */ @Override public void visit(NodeTraversal t, Node n, Node parent) { super.visit(t, n, parent); switch (n.getType()) { case Token.VAR: // Handle typedefs. if (n.hasOneChild()) { checkForTypedef(t, n.getFirstChild(), n.getJSDocInfo()); } break; } } @Override void maybeDeclareQualifiedName( NodeTraversal t, JSDocInfo info, Node n, Node parent, Node rhsValue) { checkForTypedef(t, n, info); super.maybeDeclareQualifiedName(t, info, n, parent, rhsValue); } /** * Handle typedefs. * @param t The current traversal. * @param candidate A qualified name node. * @param info JSDoc comments. */ private void checkForTypedef( NodeTraversal t, Node candidate, JSDocInfo info) { if (info == null || !info.hasTypedefType()) { return; }

Closure, 120

<FILEB>
<CHANGES>
if (ref.getSymbol().getScope()!= ref.scope) {
return false;
}
<CHANGEE>
<FILEE>
<FILEB> return references.get(i); } } return null; } /** * @return Whether the variable is only assigned a value once for its * lifetime. */ boolean isAssignedOnceInLifetime() { Reference ref = getOneAndOnlyAssignment(); if (ref == null) { return false; } // Make sure this assignment is not in a loop. for (BasicBlock block = ref.getBasicBlock(); block != null; block = block.getParent()) { if (block.isFunction) { <CHANGES> <CHANGEE> break; } else if (block.isLoop) { return false; } } return true; } /** * @return The one and only assignment. Returns if there are 0 or 2+ * assignments. */ private Reference getOneAndOnlyAssignment() { <FILEE> <SCANS> String typedef = candidate.getQualifiedName(); if (typedef == null) { return; } // TODO(nicksantos|user): This is a terrible, terrible hack // to bail out on recursive typedefs. We'll eventually need // to handle these properly. typeRegistry.declareType(typedef, unknownType); JSType realType = info.getTypedefType().evaluate(scope, typeRegistry); if (realType == null) { compiler.report( JSError.make( t.getSourceName(), candidate, MALFORMED_TYPEDEF, typedef)); } typeRegistry.overwriteDeclaredType(typedef, realType); if (candidate.isGetProp()) { defineSlot(candidate, candidate.getParent(), getNativeType(NO_TYPE), false); } } } // end GlobalScopeBuilder /** * A shallow traversal of a local scope to find all arguments and * local variables. */ private final class LocalScopeBuilder extends AbstractScopeBuilder { /** * @param scope The scope that we're building. */ private LocalScopeBuilder(Scope scope) { super(scope); } /** * Traverse the scope root and build it. */ void build() { NodeTraversal.traverse(compiler, scope.getRootNode(), this); AstFunctionContents contents = getFunctionAnalysisResults(scope.getRootNode()); if (contents != null) { for (String varName : contents.getEscapedVarNames()) { Var v = scope.getVar(varName); Preconditions.checkState(v.getScope() == scope); v.markEscaped(); } for (Multiset.Entry<String> entry : contents.getAssignedNameCounts().entrySet()) { Var v = scope.getVar(entry.getElement()); Preconditions.checkState(v.getScope() == scope); if (entry.getCount() == 1) { v.markAssignedExactlyOnce(); } } } } /** * Visit a node in a local scope, and add any local variables or catch * parameters into the local symbol table. * * @param t The node traversal. * @param n The node being visited. * @param parent The parent of n */ @Override public void visit(NodeTraversal t, Node n, Node parent) { if (n == scope.getRootNode()) { return; } if (n.isParamList() && parent == scope.getRootNode()) { handleFunctionInputs(parent); return; } super.visit(t, n, parent); } /** Handle bleeding functions and function parameters. */ private void handleFunctionInputs(Node fnNode) { // Handle bleeding functions. Node fnNameNode = fnNode.getFirstChild(); String fnName = fnNameNode.getString(); if (!fnName.isEmpty()) { Scope.Var fnVar = scope.getVar(fnName); if (fnVar == null || // Make sure we're not touching a native function. Native // functions aren't bleeding, but may not have a declaration // node. (fnVar.getNameNode() != null && // Make sure that the function is actually bleeding by checking // if has already been declared. fnVar.getInitialValue() != fnNode)) { defineSlot(fnNameNode, fnNode, fnNode.getJSType(), false); } } declareArguments(fnNode); } /** * Declares all of a function's arguments. */ private void declareArguments(Node functionNode) { Node astParameters = functionNode.getFirstChild().getNext(); Node iifeArgumentNode = null; if

Closure, 120

<FILEB>
<CHANGES>
if (ref.getSymbol().getScope()!= ref.scope) {
return false;
}
<CHANGEE>
<FILEE>
<FILEB> return references.get(i); } } return null; } /** * @return Whether the variable is only assigned a value once for its * lifetime. */ boolean isAssignedOnceInLifetime() { Reference ref = getOneAndOnlyAssignment(); if (ref == null) { return false; } // Make sure this assignment is not in a loop. for (BasicBlock block = ref.getBasicBlock(); block != null; block = block.getParent()) { if (block.isFunction) { <CHANGES> <CHANGEE> break; } else if (block.isLoop) { return false; } } return true; } /** * @return The one and only assignment. Returns if there are 0 or 2+ * assignments. */ private Reference getOneAndOnlyAssignment() { <FILEE> <SCANS> (NodeUtil.isCallOrNewTarget(functionNode)) { iifeArgumentNode = functionNode.getNext(); } FunctionType functionType = JSType.toMaybeFunctionType(functionNode.getJSType()); if (functionType != null) { Node jsDocParameters = functionType.getParametersNode(); if (jsDocParameters != null) { Node jsDocParameter = jsDocParameters.getFirstChild(); for (Node astParameter : astParameters.children()) { JSType paramType = jsDocParameter == null ? unknownType : jsDocParameter.getJSType(); boolean inferred = paramType == null || paramType == unknownType; if (iifeArgumentNode != null && inferred) { String argumentName = iifeArgumentNode.getQualifiedName(); Var argumentVar = argumentName == null || scope.getParent() == null ? null : scope.getParent().getVar(argumentName); if (argumentVar != null && !argumentVar.isTypeInferred()) { paramType = argumentVar.getType(); } } if (paramType == null) { paramType = unknownType; } defineSlot(astParameter, functionNode, paramType, inferred); if (jsDocParameter != null) { jsDocParameter = jsDocParameter.getNext(); } if (iifeArgumentNode != null) { iifeArgumentNode = iifeArgumentNode.getNext(); } } } } } // end declareArguments } // end LocalScopeBuilder /** * Does a first-order function analysis that just looks at simple things * like what variables are escaped, and whether 'this' is used. */ private static class FirstOrderFunctionAnalyzer extends AbstractScopedCallback implements CompilerPass { private final AbstractCompiler compiler; private final Map<Node, AstFunctionContents> data; FirstOrderFunctionAnalyzer( AbstractCompiler compiler, Map<Node, AstFunctionContents> outParam) { this.compiler = compiler; this.data = outParam; } @Override public void process(Node externs, Node root) { if (externs == null) { NodeTraversal.traverse(compiler, root, this); } else { NodeTraversal.traverseRoots( compiler, ImmutableList.of(externs, root), this); } } @Override public void enterScope(NodeTraversal t) { if (!t.inGlobalScope()) { Node n = t.getScopeRoot(); data.put(n, new AstFunctionContents(n)); } } @Override public void visit(NodeTraversal t, Node n, Node parent) { if (t.inGlobalScope()) { return; } if (n.isReturn() && n.getFirstChild() != null) { data.get(t.getScopeRoot()).recordNonEmptyReturn(); } if (t.getScopeDepth() <= 1) { // The first-order function analyzer looks at two types of variables: // // 1) Local variables that are assigned in inner scopes ("escaped vars") // // 2) Local variables that are assigned more than once. // // We treat all global variables as escaped by default, so there's // no reason to do this extra computation for them. return; } if (n.isName() && NodeUtil.isLValue(n) && // Be careful of bleeding functions, which create variables // in the inner scope, not the scope where the name appears. !NodeUtil.isBleedingFunctionName(n)) { String name = n.getString(); Scope scope = t.getScope(); Var var = scope.getVar(name); if (var != null) { Scope ownerScope

Closure, 120

<FILEB>
<CHANGES>
if (ref.getSymbol().getScope()!= ref.scope) {
return false;
}
<CHANGEE>
<FILEE>
<FILEB> return references.get(i); } } return null; } /** * @return Whether the variable is only assigned a value once for its * lifetime. */ boolean isAssignedOnceInLifetime() { Reference ref = getOneAndOnlyAssignment(); if (ref == null) { return false; } // Make sure this assignment is not in a loop. for (BasicBlock block = ref.getBasicBlock(); block != null; block = block.getParent()) { if (block.isFunction) { <CHANGES> <CHANGEE> break; } else if (block.isLoop) { return false; } } return true; } /** * @return The one and only assignment. Returns if there are 0 or 2+ * assignments. */ private Reference getOneAndOnlyAssignment() { <FILEE> <SCANS> = var.getScope(); if (ownerScope.isLocal()) { data.get(ownerScope.getRootNode()).recordAssignedName(name); } if (scope != ownerScope && ownerScope.isLocal()) { data.get(ownerScope.getRootNode()).recordEscapedVarName(name); } } } else if (n.isGetProp() && n.isUnscopedQualifiedName() && NodeUtil.isLValue(n)) { String name = NodeUtil.getRootOfQualifiedName(n).getString(); Scope scope = t.getScope(); Var var = scope.getVar(name); if (var != null) { Scope ownerScope = var.getScope(); if (scope != ownerScope && ownerScope.isLocal()) { data.get(ownerScope.getRootNode()) .recordEscapedQualifiedName(n.getQualifiedName()); } } } } } private AstFunctionContents getFunctionAnalysisResults(@Nullable Node n) { if (n == null) { return null; } // Sometimes this will return null in things like // NameReferenceGraphConstruction that build partial scopes. return functionAnalysisResults.get(n); } }

Closure, 120

<FILEB>
<CHANGES>
if (ref.getSymbol().getScope()!= ref.scope) {
return false;
}
<CHANGEE>
<FILEE>
<FILEB> return references.get(i); } } return null; } /** * @return Whether the variable is only assigned a value once for its * lifetime. */ boolean isAssignedOnceInLifetime() { Reference ref = getOneAndOnlyAssignment(); if (ref == null) { return false; } // Make sure this assignment is not in a loop. for (BasicBlock block = ref.getBasicBlock(); block != null; block = block.getParent()) { if (block.isFunction) { <CHANGES> <CHANGEE> break; } else if (block.isLoop) { return false; } } return true; } /** * @return The one and only assignment. Returns if there are 0 or 2+ * assignments. */ private Reference getOneAndOnlyAssignment() { <FILEE> <SCANS>/* * * ***** BEGIN LICENSE BLOCK ***** * Version: MPL 1.1/GPL 2.0 * * The contents of this file are subject to the Mozilla Public License Version * 1.1 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * http://www.mozilla.org/MPL/ * * Software distributed under the License is distributed on an "AS IS" basis, * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License * for the specific language governing rights and limitations under the * License. * * The Original Code is Rhino code, released * May 6, 1999. * * The Initial Developer of the Original Code is * Netscape Communications Corporation. * Portions created by the Initial Developer are Copyright (C) 1997-1999 * the Initial Developer. All Rights Reserved. * * Contributor(s): * Nick Santos * Google Inc. * * Alternatively, the contents of this file may be used under the terms of * the GNU General Public License Version 2 or later (the "GPL"), in which * case the provisions of the GPL are applicable instead of those above. If * you wish to allow use of your version of this file only under the terms of * the GPL and not to allow others to use your version of this file under the * MPL, indicate your decision by deleting the provisions above and replacing * them with the notice and other provisions required by the GPL. If you do * not delete the provisions above, a recipient may use your version of this * file under either the MPL or the GPL. * * ***** END LICENSE BLOCK ***** */ package com.google.javascript.rhino.jstype; import com.google.javascript.rhino.JSDocInfo; import com.google.javascript.rhino.Node; import java.io.Serializable; /** * A property slot of an object. * @author nicksantos@google.com (Nick Santos) */ public final class Property implements Serializable, StaticSlot<JSType>, StaticReference<JSType> { private static final long serialVersionUID = 1L; /** * Property's name. */ private final String name; /** * Property's type. */ private JSType type; /** * Whether the property's type is inferred. */ private final boolean inferred; /** * The node corresponding to this property, e.g., a GETPROP node that * declares this property. */ private Node propertyNode; /** The JSDocInfo for this property. */ private JSDocInfo docInfo = null; Property(String name, JSType type, boolean inferred, Node propertyNode) { this.name = name; this.type = type; this.inferred = inferred; this.propertyNode = propertyNode; } @Override public String getName() { return name; } @Override public Node getNode() { return propertyNode; } @Override public StaticSourceFile getSourceFile() { return propertyNode == null ? null : propertyNode.getStaticSourceFile(); } @Override public Property getSymbol() { return this; } @Override public Property getDeclaration() { return propertyNode == null ? null : this; } @Override public JSType getType() { return type; } @Override public boolean isTypeInferred() { return inferred; } boolean isFromExterns() { return propertyNode ==

Closure, 120

<FILEB>
<CHANGES>
if (ref.getSymbol().getScope()!= ref.scope) {
return false;
}
<CHANGEE>
<FILEE>
<FILEB> return references.get(i); } } return null; } /** * @return Whether the variable is only assigned a value once for its * lifetime. */ boolean isAssignedOnceInLifetime() { Reference ref = getOneAndOnlyAssignment(); if (ref == null) { return false; } // Make sure this assignment is not in a loop. for (BasicBlock block = ref.getBasicBlock(); block != null; block = block.getParent()) { if (block.isFunction) { <CHANGES> <CHANGEE> break; } else if (block.isLoop) { return false; } } return true; } /** * @return The one and only assignment. Returns if there are 0 or 2+ * assignments. */ private Reference getOneAndOnlyAssignment() { <FILEE> <SCANS>Constructor()}). */ public ObjectType getInstanceType() { Preconditions.checkState(hasInstanceType()); return typeOfThis.toObjectType(); } /** * Sets the instance type. This should only be used for special * native types. */ void setInstanceType(ObjectType instanceType) { typeOfThis = instanceType; } /** * Returns whether this function type has an instance type. */ public boolean hasInstanceType() { return isConstructor() || isInterface(); } /** * Gets the type of {@code this} in this function. */ @Override public JSType getTypeOfThis() { return typeOfThis.isEmptyType() ? registry.getNativeObjectType(JSTypeNative.UNKNOWN_TYPE) : typeOfThis; } /** * Gets the source node or null if this is an unknown function. */ public Node getSource() { return source; } /** * Sets the source node. */ public void setSource(Node source) { if (prototypeSlot != null) { // NOTE(bashir): On one hand when source is null we want to drop any // references to old nodes retained in prototypeSlot. On the other hand // we cannot simply drop prototypeSlot, so we retain all information // except the propertyNode for which we use an approximation! These // details mostly matter in hot-swap passes. if (source == null || prototypeSlot.getNode() == null) { prototypeSlot = new Property(prototypeSlot.getName(), prototypeSlot.getType(), prototypeSlot.isTypeInferred(), source); } } this.source = source; } /** Adds a type to the list of subtypes for this type. */ private void addSubType(FunctionType subType) { if (subTypes == null) { subTypes = Lists.newArrayList(); } subTypes.add(subType); } @Override public void clearCachedValues() { super.clearCachedValues(); if (subTypes != null) { for (FunctionType subType : subTypes) { subType.clearCachedValues(); } } if (!isNativeObjectType()) { if (hasInstanceType()) { getInstanceType().clearCachedValues(); } if (prototypeSlot != null) { ((ObjectType) prototypeSlot.getType()).clearCachedValues(); } } } /** * Returns a list of types that are subtypes of this type. This is only valid * for constructor functions, and may be null. This allows a downward * traversal of the subtype graph. */ public List<FunctionType> getSubTypes() { return subTypes; } @Override public boolean hasCachedValues() { return prototypeSlot != null || super.hasCachedValues(); } @Override JSType resolveInternal(ErrorReporter t, StaticScope<JSType> scope) { setResolvedTypeInternal(this); call = (ArrowType) safeResolve(call, t, scope); if (prototypeSlot != null) { prototypeSlot.setType( safeResolve(prototypeSlot.getType(), t, scope)); } // Warning about typeOfThis if it doesn't resolve to an ObjectType // is handled further upstream. // // TODO(nicksantos): Handle this correctly if we have a UnionType. // // TODO(nicksantos): In ES3, the run-time coerces "null" to the global // activation object. In ES5, it leaves it as null. Just punt on this // issue for now by coercing out null. This is complicated by the // fact that when most people write @this {Foo}, they really don't // mean "nullable Foo". For certain tags (like @extends

Closure, 120

<FILEB>
<CHANGES>
if (ref.getSymbol().getScope()!= ref.scope) {
return false;
}
<CHANGEE>
<FILEE>
<FILEB> return references.get(i); } } return null; } /** * @return Whether the variable is only assigned a value once for its * lifetime. */ boolean isAssignedOnceInLifetime() { Reference ref = getOneAndOnlyAssignment(); if (ref == null) { return false; } // Make sure this assignment is not in a loop. for (BasicBlock block = ref.getBasicBlock(); block != null; block = block.getParent()) { if (block.isFunction) { <CHANGES> <CHANGEE> break; } else if (block.isLoop) { return false; } } return true; } /** * @return The one and only assignment. Returns if there are 0 or 2+ * assignments. */ private Reference getOneAndOnlyAssignment() { <FILEE> <SCANS>) we de-nullify // the name for them. JSType maybeTypeOfThis = safeResolve(typeOfThis, t, scope); if (maybeTypeOfThis != null) { maybeTypeOfThis = maybeTypeOfThis.restrictByNotNullOrUndefined(); } if (maybeTypeOfThis instanceof ObjectType) { typeOfThis = maybeTypeOfThis; } boolean changed = false; ImmutableList.Builder<ObjectType> resolvedInterfaces = ImmutableList.builder(); for (ObjectType iface : implementedInterfaces) { ObjectType resolvedIface = (ObjectType) iface.resolve(t, scope); resolvedInterfaces.add(resolvedIface); changed |= (resolvedIface != iface); } if (changed) { implementedInterfaces = resolvedInterfaces.build(); } if (subTypes != null) { for (int i = 0; i < subTypes.size(); i++) { subTypes.set( i, JSType.toMaybeFunctionType(subTypes.get(i).resolve(t, scope))); } } return super.resolveInternal(t, scope); } @Override public String toDebugHashCodeString() { if (this == registry.getNativeType(JSTypeNative.FUNCTION_INSTANCE_TYPE)) { return super.toDebugHashCodeString(); } StringBuilder b = new StringBuilder(32); b.append("function ("); int paramNum = call.parameters.getChildCount(); boolean hasKnownTypeOfThis = !typeOfThis.isUnknownType(); if (hasKnownTypeOfThis) { b.append("this:"); b.append(getDebugHashCodeStringOf(typeOfThis)); } if (paramNum > 0) { if (hasKnownTypeOfThis) { b.append(", "); } Node p = call.parameters.getFirstChild(); b.append(getDebugHashCodeStringOf(p.getJSType())); p = p.getNext(); while (p != null) { b.append(", "); b.append(getDebugHashCodeStringOf(p.getJSType())); p = p.getNext(); } } b.append(")"); b.append(": "); b.append(getDebugHashCodeStringOf(call.returnType)); return b.toString(); } private String getDebugHashCodeStringOf(JSType type) { if (type == this) { return "me"; } else { return type.toDebugHashCodeString(); } } /** Create a new constructor with the parameters and return type stripped. */ public FunctionType cloneWithoutArrowType() { FunctionType result = new FunctionType( registry, getReferenceName(), source, registry.createArrowType(null, null), getInstanceType(), null, true, false); result.setPrototypeBasedOn(getInstanceType()); return result; } @Override public boolean hasAnyTemplateTypesInternal() { return getTemplateTypeMap().numUnfilledTemplateKeys() > 0 || typeOfThis.hasAnyTemplateTypes() || call.hasAnyTemplateTypes(); } }